x1ongzhu 2 лет назад
Родитель
Сommit
5f6db1c69a

+ 1 - 1
index.html

@@ -7,7 +7,7 @@
 	<link rel="apple-touch-icon" href="/favicon.ico">
 	<meta name="viewport"
 		content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-	<title>ChatGPT Web</title>
+	<title>CHILLGPT</title>
 </head>
 
 <body class="dark:bg-black">

+ 3 - 2
src/App.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { NConfigProvider } from 'naive-ui'
+import { NConfigProvider, NGlobalStyle } from 'naive-ui'
 import { NaiveProvider } from '@/components/common'
 import { useTheme } from '@/hooks/useTheme'
 import { useLanguage } from '@/hooks/useLanguage'
@@ -9,7 +9,8 @@ const { language } = useLanguage()
 </script>
 
 <template>
-    <NConfigProvider class="h-full" :theme="theme" :theme-overrides="themeOverrides" :locale="language">
+    <NConfigProvider class="h-full" :theme="theme" :theme-overrides="themeOverrides" :locale="language" inline-theme-disabled>
+        <NGlobalStyle/>
         <NaiveProvider>
             <RouterView />
         </NaiveProvider>

BIN
src/assets/avatar.jpg


BIN
src/assets/bg_desktop.jpg


+ 0 - 0
src/assets/png-shouye.jpg → src/assets/bg_mobile.jpg


+ 0 - 0
src/assets/png-CHILLGPT.png → src/assets/brand.png


BIN
src/assets/code.png


BIN
src/assets/icon_jieshao.png


BIN
src/assets/png-bg.jpg


BIN
src/assets/user.png


+ 81 - 34
src/components/common/LoginForm/index.vue

@@ -1,51 +1,71 @@
 <template>
-    <n-form ref="loginForm" :model="form" :rules="rules" :show-label="false">
-        <n-form-item ref="phoneRef" path="phone" label="">
-            <n-input v-model:value="form.phone" :input-props="{ type: 'tel' }" @keydown.enter.prevent>
-                <template #prefix>
-                    <n-icon size="24" class="input-icon">
-                        <user />
-                    </n-icon>
-                </template>
-            </n-input>
-        </n-form-item>
-        <n-form-item path="code" label="">
-            <n-input-group style="align-items: center">
-                <n-input v-model:value="form.code" :input-props="{ type: 'tel' }">
+    <NConfigProvider :theme-overrides="themeOverrides">
+        <n-form ref="loginForm" :model="form" :rules="rules" :show-label="false">
+            <n-form-item ref="phoneRef" path="phone" label="">
+                <n-input v-model:value="form.phone" placeholder="请输入手机号" :allow-input="onlyAllowNumber">
                     <template #prefix>
                         <n-icon size="24" class="input-icon">
-                            <Lock />
+                            <user />
                         </n-icon>
                     </template>
                 </n-input>
-                <div style="min-width: 20px"></div>
-                <n-button :disabled="countDown > 0" type="tertiary" secondary @click="sendVerify">
-                    {{ countDown > 0 ? `${countDown}秒后重新发送` : '发送验证码' }}
-                </n-button>
-            </n-input-group>
-        </n-form-item>
+            </n-form-item>
+            <n-form-item path="code" label="">
+                <div class="code-wrapper">
+                    <n-input v-model:value="form.code" placeholder="请输入验证码" :allow-input="onlyAllowNumber">
+                        <template #prefix>
+                            <n-icon size="24" class="input-icon">
+                                <Lock />
+                            </n-icon>
+                        </template>
+                    </n-input>
+                    <n-button :disabled="countDown > 0" type="primary" secondary @click="sendVerify">
+                        {{ countDown > 0 ? `${countDown}秒后重新发送` : '发送验证码' }}
+                    </n-button>
+                </div>
+            </n-form-item>
+            <div class="submit">
+                <n-button @click="submit" block type="primary" size="large" :loading="loading" circle> 登录 </n-button>
+            </div>
 
-        <div class="check">
-            <n-checkbox v-model:checked="agree">
-                已阅读同意
-                <span class="prim" @click.stop="">《用户服务协议》</span>和
-                <span class="prim" @click.stop="">《平台隐私协》</span>
-            </n-checkbox>
-        </div>
-
-        <div class="submit">
-            <n-button @click="submit" block type="primary" size="large" :loading="loading" circle> 登录 </n-button>
-        </div>
-    </n-form>
+            <n-el class="agree">
+                <n-checkbox v-model:checked="agree">
+                    已阅读同意
+                    <span class="prim" @click.stop="">《用户服务协议》</span>和
+                    <span class="prim" @click.stop="">《平台隐私协》</span>
+                </n-checkbox>
+            </n-el>
+        </n-form>
+    </NConfigProvider>
 </template>
 <script setup lang="ts">
 import { ref, Ref } from 'vue'
-import { NForm, NFormItem, NInput, NInputGroup, NCheckbox, NButton, NIcon, useMessage, FormInst } from 'naive-ui'
+import {
+    NForm,
+    NFormItem,
+    NInput,
+    NCheckbox,
+    NButton,
+    NIcon,
+    useMessage,
+    FormInst,
+    NEl,
+    NConfigProvider,
+    GlobalThemeOverrides
+} from 'naive-ui'
 import { User, Lock } from '@vicons/tabler'
 import { fetchSendVerify } from '../../../api'
 import { useStorage } from '@vueuse/core'
 import { useAuthStore } from '@/store'
 
+const themeOverrides: GlobalThemeOverrides = {
+    Input: {
+        heightMedium: '40px'
+    },
+    Button: {
+        heightMedium: '40px'
+    }
+}
 const emit = defineEmits(['success'])
 const ms = useMessage()
 const authStore = useAuthStore()
@@ -83,6 +103,7 @@ const rules = {
         }
     ]
 }
+const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
 const countDown = ref(0)
 const agree = useStorage('agree', false)
 const loading = ref(false)
@@ -137,13 +158,39 @@ async function submit() {
     color: var(--n-text-color) !important;
     margin-right: 12px;
 }
+.code-wrapper {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    .n-input {
+        flex: 1 1 0;
+    }
+    .n-button {
+        margin-left: 10px;
+    }
+}
 .btn-send {
     margin-left: 20px !important;
 }
 .submit {
-    margin-top: 40px;
+    margin-top: 10px;
     .n-button {
         height: 40px;
     }
 }
+.agree {
+    margin-top: 20px;
+    color: var(--text-color-3);
+    text-align: center;
+    :deep(.n-checkbox) {
+        font-size: 12px;
+        line-height: 24px;
+    }
+    :deep(.n-checkbox__label) {
+        color: var(--text-color-3);
+    }
+    .prim {
+        color: var(--primary-color);
+    }
+}
 </style>

+ 13 - 15
src/hooks/useTheme.ts

@@ -18,22 +18,20 @@ export function useTheme() {
     })
 
     const themeOverrides = computed<GlobalThemeOverrides>(() => {
-        if (isDark.value) {
-            return {
-                // common: {
-                //     primaryColor: '#B17DFF',
-                //     primaryColorHover: '#B17DFF',
-                //     primaryColorPressed: '#9265D5'
-                // },
-                // Button: {
-                //     heightLarge: '38px'
-                // },
-                // Input: {
-                //     textColor: '#ffffff'
-                // }
-            }
+        return {
+            common: {
+                primaryColor: '#B17DFF',
+                primaryColorHover: '#BF95FF',
+                primaryColorPressed: '#9265D5',
+                primaryColorSuppl: '#B17DFF',
+            },
+            Button: {
+                heightLarge: '38px'
+            },
+            // Input: {
+            //     textColor: '#ffffff'
+            // }
         }
-        return {}
     })
 
     watch(

+ 12 - 16
src/store/modules/app/index.ts

@@ -1,34 +1,30 @@
 import { defineStore } from 'pinia'
 import type { AppState, Language, Theme } from './helper'
-import { getLocalSetting, setLocalSetting } from './helper'
-import { store } from '@/store'
+import { useStorage } from '@vueuse/core'
+
+const appState = useStorage<AppState>('appState', { siderCollapsed: false, theme: 'auto', language: 'zh-CN' })
 
 export const useAppStore = defineStore('app-store', {
-    state: (): AppState => getLocalSetting(),
+    state: (): AppState => appState.value,
+    
     actions: {
         setSiderCollapsed(collapsed: boolean) {
-            this.siderCollapsed = collapsed
-            this.recordState()
+            appState.value.siderCollapsed = collapsed
+            this.$state.siderCollapsed = collapsed
         },
 
         setTheme(theme: Theme) {
-            this.theme = theme
-            this.recordState()
+            appState.value.theme = theme
+            this.$state.theme = theme
         },
 
         setLanguage(language: Language) {
-            if (this.language !== language) {
-                this.language = language
-                this.recordState()
-            }
-        },
-
-        recordState() {
-            setLocalSetting(this.$state)
+            appState.value.language = language
+            this.$state.language = language
         }
     }
 })
 
 export function useAppStoreWithOut() {
-    return useAppStore(store)
+    return appState
 }

+ 10 - 11
src/styles/global.less

@@ -9,14 +9,13 @@ body {
     padding-bottom: env(safe-area-inset-bottom);
 }
 
-// :root {
-//     --prim: #b17dff;
-//     --bg2:linear-gradient(136deg, #34354F10 0%, #20223C10 100%);
-// }
+:root {
+    --bg2:linear-gradient(136deg, #34354F10 0%, #20223C10 100%);
+}
 
-// .dark{
-//     --bg2:linear-gradient(136deg, #34354F 0%, #20223C 100%);
-// }
+.dark{
+    --bg2:linear-gradient(136deg, #34354F 0%, #20223C 100%);
+}
 
 // .n-button--primary-type {
 //     background: #26f50d linear-gradient(307deg, #c072ff 0%, #f9d6ff 45%, #dfffff 89%, #f5ffff 100%);
@@ -29,7 +28,7 @@ body {
 //     }
 // }
 
-// @font-face {
-//     font-family: 'AlimamaShuHeiTi';
-//     src: url(https://cdn.raex.vip/font/2023-03-24-10-09-25HtghnVXP.ttf);
-// }
+@font-face {
+    font-family: 'AlimamaShuHeiTi';
+    src: url(https://cdn.raex.vip/font/2023-03-24-10-09-25HtghnVXP.ttf);
+}

+ 126 - 171
src/views/page/Home.vue

@@ -1,139 +1,149 @@
 <template>
-    <div class="page h-full flex flex-col" :style="{ backgroundImage: `url(${desktopMode ? pcBg : h5Bg})` }">
-        <n-page-header>
-            <template #title>
-                <span class="title">CHILLGPT </span>
-            </template>
-            <template #avatar>
-                <n-avatar :src="logo" />
-            </template>
-            <template #extra>
-                <user-avatar avatarType="small" v-if="isLogin" />
-                <template v-else>
-                    <n-button v-if="desktopMode" type="primary" round @click="showLogin = true"> 登录 </n-button>
-                    <n-button v-else @click="$router.push('/login')">登录</n-button>
+    <NConfigProvider>
+        <n-el class="page h-full flex flex-col" :style="{ backgroundImage: `url(${isMobile ? h5Bg : pcBg})` }">
+            <n-page-header>
+                <template #title>
+                    <span class="title">CHILLGPT </span>
                 </template>
-            </template>
-        </n-page-header>
+                <template #avatar>
+                    <n-avatar :src="logo" />
+                </template>
+                <template #extra>
+                    <user-avatar avatarType="small" v-if="isLogin" />
+                    <template v-else>
+                        <n-button v-if="!isMobile" type="primary" round @click="showLogin = true"> 登录 </n-button>
+                        <n-button v-else @click="$router.push('/login')">登录</n-button>
+                    </template>
+                </template>
+            </n-page-header>
 
-        <div class="flex-1 page-content">
-            <img src="@/assets/png-CHILLGPT.png" class="img1" alt="" />
+            <div class="flex-1 page-content">
+                <img src="@/assets/brand.png" class="img1" alt="" />
 
-            <div class="desc">
-                ChillGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文等任务。
-            </div>
+                <div class="desc">
+                    ChillGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文等任务。
+                </div>
 
-            <n-card
-                class="vip"
-                v-if="desktopMode"
-                title="会员权益"
-                content-style="padding:0"
-                header-style="text-align:center;padding:0;height:44px;line-height:44px;font-size: 16px;font-family: AlimamaShuHeiTi;"
-            >
-                <n-grid cols="2 s:3 m:4 l:7 xl:7 2xl:7" responsive="screen">
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-01.png" alt="" />
-                            <div class="text">
-                                <div>无限对话</div>
-                                <div>畅快聊天</div>
+                <n-card
+                    class="vip"
+                    v-if="!isMobile"
+                    title="会员权益"
+                    content-style="padding:0"
+                    header-style="text-align:center;padding:0;height:44px;line-height:44px;font-size: 16px;font-family: AlimamaShuHeiTi;color:#ffffff"
+                >
+                    <n-grid cols="2 s:3 m:4 l:7 xl:7 2xl:7" responsive="screen">
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-01.png" alt="" />
+                                <div class="text">
+                                    <div>无限对话</div>
+                                    <div>畅快聊天</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-02.png" alt="" />
-                            <div class="text">
-                                <div>推荐奖励</div>
-                                <div>一级经销商权限</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-02.png" alt="" />
+                                <div class="text">
+                                    <div>推荐奖励</div>
+                                    <div>一级经销商权限</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-03.png" alt="" />
-                            <div class="text">
-                                <div>空投福利</div>
-                                <div>高价值数字藏品</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-03.png" alt="" />
+                                <div class="text">
+                                    <div>空投福利</div>
+                                    <div>高价值数字藏品</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-04.png" alt="" />
-                            <div class="text">
-                                <div>全线3折购</div>
-                                <div>Chillgpt生态</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-04.png" alt="" />
+                                <div class="text">
+                                    <div>全线3折购</div>
+                                    <div>Chillgpt生态</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-05.png" alt="" />
-                            <div class="text">
-                                <div>测试白名单</div>
-                                <div>第一优先级</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-05.png" alt="" />
+                                <div class="text">
+                                    <div>测试白名单</div>
+                                    <div>第一优先级</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-06.png" alt="" />
-                            <div class="text">
-                                <div>Chillgpt Pix</div>
-                                <div>六个月免费会员</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-06.png" alt="" />
+                                <div class="text">
+                                    <div>Chillgpt Pix</div>
+                                    <div>六个月免费会员</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                    <n-grid-item>
-                        <div class="vip-item">
-                            <img src="@/assets/png-07.png" alt="" />
-                            <div class="text">
-                                <div>可申请Chillg</div>
-                                <div>城市级节点权限</div>
+                        </n-grid-item>
+                        <n-grid-item>
+                            <div class="vip-item">
+                                <img src="@/assets/png-07.png" alt="" />
+                                <div class="text">
+                                    <div>可申请Chillg</div>
+                                    <div>城市级节点权限</div>
+                                </div>
                             </div>
-                        </div>
-                    </n-grid-item>
-                </n-grid>
-            </n-card>
+                        </n-grid-item>
+                    </n-grid>
+                </n-card>
 
-            <div class="flex-1" v-else></div>
+                <div class="flex-1" v-else></div>
 
-            <div class="btn">
-                <n-button type="primary" block round size="large" @click="goChat"> 立即体验 </n-button>
+                <div class="btn">
+                    <n-button type="primary" block round size="large" @click="goChat"> 立即体验 </n-button>
+                </div>
             </div>
-        </div>
 
-        <n-modal v-model:show="showLogin" transform-origin="center">
-            <n-card
-                title="登录"
-                class="login"
-                :border="false"
-                header-style="text-align:center;padding:30px;font-size:24px;"
-            >
-                <div class="login-box">
-                    <login-form @success="onLogin"></login-form>
-                </div>
-            </n-card>
-        </n-modal>
-    </div>
+            <n-modal v-model:show="showLogin" transform-origin="center">
+                <n-card
+                    title="登录"
+                    class="login"
+                    :border="false"
+                    header-style="text-align:center;padding:30px;font-size:24px;"
+                >
+                    <div class="login-box">
+                        <login-form @success="onLogin"></login-form>
+                    </div>
+                </n-card>
+            </n-modal>
+        </n-el>
+    </NConfigProvider>
 </template>
 
 <script setup lang="ts">
-import { NAvatar, NButton, NPageHeader, NCard, NGrid, NGridItem, NModal } from 'naive-ui'
-import h5Bg from '@/assets/png-shouye.jpg'
-import pcBg from '@/assets/png-bg.jpg'
+import {
+    NAvatar,
+    NButton,
+    NPageHeader,
+    NCard,
+    NGrid,
+    NGridItem,
+    NModal,
+    NConfigProvider,
+    darkTheme,
+    NEl
+} from 'naive-ui'
+import h5Bg from '@/assets/bg_mobile.jpg'
+import pcBg from '@/assets/bg_desktop.jpg'
 import logo from '@/assets/logo.png'
 import { useUserStore } from '@/store'
 import { useRouter } from 'vue-router'
 import { ref, computed } from 'vue'
 import { UserAvatar, LoginForm } from '@/components/common'
-import { useWindowSize } from '@vueuse/core'
+import { useBasicLayout } from '@/hooks/useBasicLayout'
 
-const { width } = useWindowSize()
-const desktopMode = computed(() => {
-    return width.value > 768
-})
+const { isMobile } = useBasicLayout()
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -150,13 +160,13 @@ const onLogin = () => {
 
 function goChat() {
     if (!userStore.userInfo.id) {
-        if (desktopMode.value) {
+        if (!isMobile.value) {
             showLogin.value = true
         } else {
-            router.push('/login')
+            router.push({ name: 'login' })
         }
     } else {
-        router.push('/chat')
+        router.push({ name: 'Chat' })
     }
 }
 </script>
@@ -210,6 +220,7 @@ function goChat() {
         border-radius: 12px;
         border: 1px solid rgba(255, 255, 255, 0.2);
         margin-top: 30px;
+        overflow: hidden;
     }
 
     .btn {
@@ -268,69 +279,13 @@ function goChat() {
 
 .login {
     width: 560px;
-
-    background: #0a0b21;
+    // background: #0a0b21;
     border-radius: 12px;
 }
 
-.input-pre {
-    width: 24px;
-    height: 24px;
-    margin-right: 12px;
-}
 .login-box {
     width: 334px;
     margin: 20px auto 0;
     padding-bottom: 60px;
-
-    .submit {
-        margin-top: 40px;
-        .n-button {
-            height: 40px;
-        }
-    }
-}
-:deep(.n-input) {
-    --n-border-radius: 0 !important;
-    --n-color: rgba(255, 255, 255, 0) !important;
-
-    .n-input__input-el {
-        --n-height: 50px;
-    }
-    .n-input-wrapper {
-        --n-padding-left: 0;
-        --n-padding-right: 0;
-    }
-
-    .n-input__border {
-        border-top-width: 0px !important;
-        border-left-width: 0px !important;
-        border-right-width: 0px !important;
-    }
-    .n-input__state-border {
-        border-top-width: 0px !important;
-        border-left-width: 0px !important;
-        border-right-width: 0px !important;
-    }
-}
-
-.send {
-    margin-left: 20px !important;
-    margin-top: 8px;
-}
-
-:deep(.n-checkbox) {
-    --n-font-size: 12px !important;
-    --n-text-color: #939599 !important;
-    .n-checkbox-box {
-        --n-size: 16px;
-        --n-border-radius: 100px;
-        --n-border: 1px solid #c8c9cc;
-        --n-check-mark-color: #fff;
-    }
-
-    .prim {
-        color: var(--prim);
-    }
 }
 </style>

+ 28 - 229
src/views/page/Login.vue

@@ -1,150 +1,30 @@
 <template>
-    <div class="page">
-        <div class="back" @click="goBack">
-            <img src="@/assets/icon_fanhui.png" alt="" />
+    <n-el class="page">
+        <n-el class="back" @click="$router.go(-1)">
+            <n-icon size="24">
+                <ChevronLeft />
+            </n-icon>
+        </n-el>
+        <n-el class="title">
+            <n-el class="text1">登陆ChillGPT</n-el>
+            <n-el class="text2">未注册手机验证后自动注册登录</n-el>
+        </n-el>
+
+        <div class="login-wrapper">
+            <login-form @success="onLogin"></login-form>
         </div>
-        <template v-if="step == 0">
-            <div class="title">
-                <div class="text1">登陆ChillGPT</div>
-                <div class="text2">未注册手机验证后自动注册登陆</div>
-            </div>
-
-            <n-form ref="formRef" :model="form" :rules="rules" :show-label="false">
-                <n-form-item path="phone" label="">
-                    <n-input :input-props="{ type: 'tel' }" v-model:value="form.phone" clearable />
-                </n-form-item>
-                <div class="submit">
-                    <n-button block type="primary" size="large" :loading="loading" circle @click="send">
-                        获取验证码
-                    </n-button>
-                </div>
-
-                <div class="check">
-                    <n-checkbox v-model:checked="agree">
-                        已阅读同意
-                        <span class="prim" @click.stop="">《用户服务协议》</span>和
-                        <span class="prim" @click.stop=""> 《平台隐私协》 </span>
-                    </n-checkbox>
-                </div>
-            </n-form>
-        </template>
-
-        <template v-else>
-            <div class="title">
-                <div class="text1">输入验证码</div>
-                <div class="text2">验证码已发送到 {{ showPhone }}</div>
-            </div>
-
-            <n-form ref="formCode" :show-label="false">
-                <n-form-item path="code" label="">
-                    <div class="code">
-                        <n-input
-                            class="code-input"
-                            v-model:value="form.code"
-                            maxlength="4"
-                            clearable
-                            :on-input="onCodeInput"
-                            :disabled="loading"
-                        />
-                        <div class="dvCode" v-html="codeStr"></div>
-                    </div>
-                </n-form-item>
-                <!-- <div class="submit">
-                    <n-button block type="primary" size="large" circle @click="send"> 立即登录 </n-button>
-                </div> -->
-            </n-form>
-        </template>
-    </div>
+    </n-el>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue'
-import { NForm, NFormItem, NInput, NButton, NCheckbox, useMessage } from 'naive-ui'
-import { fetchSendVerify } from '@/api'
-import { useUserStore, useAuthStore } from '@/store'
+import { NIcon, NEl } from 'naive-ui'
 import { useRouter } from 'vue-router'
+import { ChevronLeft } from '@vicons/tabler'
+import { LoginForm } from '@/components/common'
 
 const router = useRouter()
-const userStore = useUserStore()
-const authStore = useAuthStore()
-
-const form = ref({
-    phone: '',
-    code: ''
-})
-const step = ref(0)
-const agree = ref(false)
-const message = useMessage()
-const formRef: any = ref(null)
-const loading = ref(false)
-const rules = {
-    phone: [
-        {
-            validator(rule: any, value: any) {
-                if (!value) {
-                    return new Error('请输入手机号')
-                } else if (!/^1[3-9]\d{9}$/.test(value)) {
-                    return new Error('手机号格式错误')
-                }
-
-                return true
-            },
-            trigger: ['blur']
-        }
-    ]
-}
-async function send() {
-    if (!agree.value) {
-        message.error('请先阅读并同意协议')
-        return
-    }
-    try {
-        await formRef.value.validate().catch((e: any) => {
-            throw 'validate error'
-        })
-        loading.value = true
-        await fetchSendVerify(form.value.phone)
-        loading.value = false
-        step.value = 1
-    } catch (e: any) {
-        loading.value = false
-        if (e === 'validate error') return
-        message.error(e.message)
-    }
-}
-
-const showPhone = computed(() => {
-    return form.value.phone.substr(0, 3) + '****' + form.value.phone.substr(-4)
-})
-
-const codeStr = computed(() => {
-    let str = ''
-    for (let i = 0; i < 4; i++) {
-        str += '<span>' + (form.value.code.length > i ? form.value.code[i] : '') + '</span>'
-    }
-    return str
-})
-
-async function onCodeInput(e: any) {
-    if (e.length === 4) {
-        try {
-            loading.value = true
-            await authStore.phoneLogin(form.value.phone, form.value.code)
-            message.success('登录成功')
-            router.replace({ name: 'Chat' })
-        } catch (e: any) {
-            console.log(e)
-            loading.value = false
-            message.error(e.message)
-        }
-    }
-}
-function goBack() {
-    if (step.value === 1) {
-        step.value = 0
-    } else {
-        router.back()
-    }
+function onLogin() {
+    router.replace({ name: 'Chat' })
 }
 </script>
 
@@ -156,109 +36,28 @@ function goBack() {
     .text1 {
         font-size: 24px;
         font-weight: bold;
-        color: #ffffff;
         line-height: 30px;
+        color: var(--text-color-1);
     }
 
     .text2 {
         font-size: 12px;
-        color: #939599;
         line-height: 24px;
         margin-top: 8px;
+        color: var(--text-color-3);
     }
 }
 
 .back {
-    width: 24px;
-    height: 24px;
+    font-size: 0;
     position: fixed;
-    top: 13px;
-    left: 20px;
+    top: 0;
+    left: 0;
+    padding: 12px 20px;
+    color: var(--text-color-1);
 }
 
-:deep(.n-form) {
+.login-wrapper {
     padding: 38px 0;
-
-    .n-input {
-        --n-border-radius: 0 !important;
-        --n-color: rgba(255, 255, 255, 0) !important;
-        font-size: 24px;
-        font-weight: bold;
-        .n-input__input-el {
-            --n-height: 44px;
-        }
-    }
-    .n-input-wrapper {
-        --n-padding-left: 0;
-        --n-padding-right: 0;
-    }
-    .n-input__border {
-        border-top-width: 0px !important;
-        border-left-width: 0px !important;
-        border-right-width: 0px !important;
-    }
-    .n-input__state-border {
-        border-top-width: 0px !important;
-        border-left-width: 0px !important;
-        border-right-width: 0px !important;
-    }
-}
-
-.submit {
-    padding: 15px 0;
-}
-
-:deep(.n-checkbox) {
-    --n-font-size: 12px !important;
-    --n-text-color: #939599 !important;
-    .n-checkbox-box {
-        --n-size: 16px;
-        --n-border-radius: 100px;
-        --n-border: 1px solid #c8c9cc;
-        --n-check-mark-color: #fff;
-    }
-
-    .prim {
-        color: var(--prim);
-    }
-}
-
-.check {
-    padding: 25px 0;
-}
-
-.code {
-    position: relative;
-    width: 100%;
-    height: 48px;
-
-    .code-input {
-        position: absolute;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        z-index: 200;
-        opacity: 0;
-    }
-
-    .dvCode {
-        width: 100%;
-        .f();
-        justify-content: space-between;
-        :deep(span) {
-            width: 48px;
-            height: 48px;
-            display: inline-block;
-            background: #101010;
-            box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.5);
-            border-radius: 4px;
-            border: 1px solid #ffffff;
-            font-size: 24px;
-            color: #fff;
-            line-height: 48px;
-            text-align: center;
-        }
-    }
 }
-</style>
+</style>

+ 203 - 0
src/views/page/Login1.vue

@@ -0,0 +1,203 @@
+<template>
+    <n-el class="page">
+        <n-el class="back" @click="goBack">
+            <n-icon size="24">
+                <ChevronLeft />
+            </n-icon>
+        </n-el>
+        <n-el class="title">
+            <template v-if="step === 0">
+                <n-el class="text1">登陆ChillGPT</n-el>
+                <n-el class="text2">未注册手机验证后自动注册登陆</n-el>
+            </template>
+            <template v-else>
+                <n-el class="text1">输入验证码</n-el>
+                <n-el class="text2">验证码已发送到 {{ showPhone }}</n-el>
+            </template>
+        </n-el>
+
+        <n-form class="login-form" ref="formRef" :model="loginForm" :show-label="false">
+            <n-form-item path="phone" label="" :rule="phoneRule">
+                <n-input
+                    placeholder="请输入手机号"
+                    :allow-input="onlyAllowNumber"
+                    v-model:value="loginForm.phone"
+                    clearable
+                    :disabled="loading"
+                />
+            </n-form-item>
+            <n-form-item path="code" label="" :rule="codeRule">
+                <n-input
+                    placeholder="请输入验证码"
+                    :allow-input="onlyAllowNumber"
+                    v-model:value="loginForm.code"
+                    maxlength="4"
+                    clearable
+                    :disabled="loading"
+                />
+            </n-form-item>
+            <n-form-item class="submit">
+                <n-button
+                    block
+                    type="primary"
+                    size="large"
+                    :loading="loading"
+                    circle
+                    @click="step === 0 ? send() : submit()"
+                >
+                    {{ step === 0 ? '获取验证码' : '立即登录' }}
+                </n-button>
+            </n-form-item>
+
+            <n-form-item class="check" v-if="step === 0">
+                <n-checkbox v-model:checked="agree">
+                    已阅读同意
+                    <span class="prim" @click.stop="">《用户服务协议》</span>和
+                    <span class="prim" @click.stop=""> 《平台隐私协》 </span>
+                </n-checkbox>
+            </n-form-item>
+        </n-form>
+    </n-el>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { NForm, NFormItem, NInput, NButton, NCheckbox, useMessage, NIcon, NEl, FormItemRule } from 'naive-ui'
+import { fetchSendVerify } from '@/api'
+import { useUserStore, useAuthStore } from '@/store'
+import { useRouter } from 'vue-router'
+import { ChevronLeft } from '@vicons/tabler'
+
+const router = useRouter()
+const authStore = useAuthStore()
+
+const loginForm = ref({
+    phone: '',
+    code: ''
+})
+const step = ref(1)
+const agree = ref(false)
+const message = useMessage()
+const formRef: any = ref(null)
+const loading = ref(false)
+const phoneRule: FormItemRule = {
+    type: 'regexp',
+    pattern: /^1[3-9]\d{9}$/,
+    message: '请输入正确的手机号',
+    trigger: 'submit'
+}
+const codeRule: FormItemRule = {
+    type: 'regexp',
+    pattern: /^\d{4}$/,
+    message: '请输入正确的验证码',
+    trigger: 'submit'
+}
+const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
+async function send() {
+    if (!agree.value) {
+        message.error('请先阅读并同意协议')
+        return
+    }
+    try {
+        await formRef.value.validate().catch((e: any) => {
+            throw 'validate error'
+        })
+        loading.value = true
+        await fetchSendVerify(loginForm.value.phone)
+        loading.value = false
+        step.value = 1
+    } catch (e: any) {
+        loading.value = false
+        if (e === 'validate error') return
+        message.error(e.message)
+    }
+}
+
+const showPhone = computed(() => {
+    return loginForm.value.phone.substr(0, 3) + '****' + loginForm.value.phone.substr(-4)
+})
+
+const codeStr = computed(() => {
+    let str = ''
+    for (let i = 0; i < 4; i++) {
+        str += '<span>' + (loginForm.value.code.length > i ? loginForm.value.code[i] : '') + '</span>'
+    }
+    return str
+})
+
+async function submit() {
+    try {
+        loading.value = true
+        await authStore.phoneLogin(loginForm.value.phone, loginForm.value.code)
+        message.success('登录成功')
+        router.replace({ name: 'Chat' })
+    } catch (e: any) {
+        console.log(e)
+        loading.value = false
+        message.error(e.message)
+    }
+}
+function goBack() {
+    if (step.value === 1) {
+        step.value = 0
+    } else {
+        router.back()
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.page {
+    padding: 80px 48px;
+}
+.title {
+    .text1 {
+        font-size: 24px;
+        font-weight: bold;
+        line-height: 30px;
+        color: var(--text-color-1);
+    }
+
+    .text2 {
+        font-size: 12px;
+        line-height: 24px;
+        margin-top: 8px;
+        color: var(--text-color-3);
+    }
+}
+
+.back {
+    font-size: 0;
+    position: fixed;
+    top: 0;
+    left: 0;
+    padding: 12px 20px;
+    color: var(--text-color-1);
+}
+
+.login-form {
+    padding: 38px 0;
+}
+
+.submit {
+}
+
+:deep(.n-checkbox) {
+    --n-font-size: 12px !important;
+    --n-text-color: var(--text-color-3) !important;
+    .n-checkbox-box {
+        --n-size: 16px;
+        --n-border-radius: 100px;
+        --n-border: 1px solid var(--text-color-3);
+        --n-check-mark-color: var(--text-color-1);
+    }
+
+    .prim {
+        color: var(--primary-color);
+    }
+}
+
+.check {
+    padding: 25px 0;
+}
+</style>