This commit is contained in:
scout 2024-03-06 10:09:16 +08:00
parent 515a08343f
commit cb382e5000
15 changed files with 566 additions and 30 deletions

2
components.d.ts vendored
View File

@ -15,10 +15,12 @@ declare module 'vue' {
HoverButton: typeof import('./src/components/common/HoverButton/index.vue')['default']
NaiveProvider: typeof import('./src/components/common/NaiveProvider/index.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NImage: typeof import('naive-ui')['NImage']
NImageGroup: typeof import('naive-ui')['NImageGroup']
NInput: typeof import('naive-ui')['NInput']
NModal: typeof import('naive-ui')['NModal']
NPageHeader: typeof import('naive-ui')['NPageHeader']
NPopover: typeof import('naive-ui')['NPopover']
NSpace: typeof import('naive-ui')['NSpace']
NUpload: typeof import('naive-ui')['NUpload']

View File

@ -15,6 +15,7 @@
"build": "vite build",
"preview": "vite preview",
"build-prod": "vite build --mode prod",
"prod": "vite --mode prod",
"type-check": "vue-tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",

File diff suppressed because one or more lines are too long

View File

@ -49,3 +49,27 @@ export const uploadFormData = (data) => {
data
})
}
// 登录
export const loginApi = (data) => {
return request({
url: '/user/login',
method: 'post',
data
})
}
// 获取验证码
export const getCode = (data) => {
return request({
url: '/user/send',
method: 'post',
data
})
}
// 菜单
export const getMenuApi = (data) => {
return request({
url: '/rule/rules',
method: 'post',
data
})
}

View File

@ -18,7 +18,26 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
},
{
path: '/mine',
name: 'mine',
component: () => import('@/views/mine/index.vue'),
},
{
path: '/serveInfo',
name: 'serveInfo',
component: () => import('@/views/info/serveInfo.vue'),
},
{
path: '/privateInfo',
name: 'privateInfo',
component: () => import('@/views/info/privateInfo.vue'),
},
{
path: '/404',
name: '404',

View File

@ -2,9 +2,11 @@
import axios from "axios";
import { ElLoading } from 'element-plus'
import {Local} from '@/utils/storage/storage.js'
import { router } from '@/router'
const ms = useMessage()
const request = axios.create({
baseURL:import.meta.env.VITE_APP_API_BASE_URL,
timeout:5000
timeout:60 * 60 * 1000
});
let loading
request.interceptors.request.use((config)=>{
@ -18,7 +20,12 @@ request.interceptors.request.use((config)=>{
config.headers.Authorization =Local.get('token')
return config;
});
request.interceptors.response.use((res)=>{
request.interceptors.response.use(async (res)=>{
// 如果返回401说明token过期需要重新登录
if (res && res.data.code === 401) {
// 重新登录
await router.push('/login')
}
loading?.close()
return res.data;
},()=>{

View File

@ -2,7 +2,8 @@
import { computed, nextTick } from 'vue'
import { HoverButton, SvgIcon } from '@/components/common'
import { useAppStore, useChatStore } from '@/store'
import { useRouter } from 'vue-router'
import AvatarComponent from '../Message/Avatar.vue'
interface Props {
usingContext: boolean
}
@ -15,7 +16,7 @@ interface Emit {
defineProps<Props>()
const emit = defineEmits<Emit>()
const router = useRouter()
const appStore = useAppStore()
const chatStore = useChatStore()
@ -39,27 +40,24 @@ function handleExport() {
function handleClear() {
emit('handleClear')
}
//
function goMine() {
router.push('/mine')
}
</script>
<template>
<header
style="padding-top: 30px;"
class="sticky top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur"
>
<header style="padding-top: 30px;"
class="sticky top-0 left-0 right-0 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur">
<div class="relative flex items-center justify-between min-w-0 overflow-hidden h-14">
<div class="flex items-center">
<button
class="flex items-center justify-center w-11 h-11"
@click="handleUpdateCollapsed"
>
<button class="flex items-center justify-center w-11 h-11" @click="handleUpdateCollapsed">
<SvgIcon v-if="collapsed" class="text-2xl" icon="ri:align-justify" />
<SvgIcon v-else class="text-2xl" icon="ri:align-right" />
</button>
</div>
<h1
class="flex-1 px-4 pr-6 overflow-hidden cursor-pointer select-none text-ellipsis whitespace-nowrap"
@dblclick="onScrollToTop"
>
<h1 class="flex-1 px-4 pr-6 overflow-hidden cursor-pointer select-none text-ellipsis whitespace-nowrap"
@dblclick="onScrollToTop">
{{ currentChatHistory?.title ?? '' }}
</h1>
<div class="flex items-center space-x-2">
@ -73,6 +71,7 @@ function handleClear() {
<SvgIcon icon="ri:delete-bin-line" />
</span>
</HoverButton> -->
<AvatarComponent :image="true" @click="goMine" />
</div>
</div>
</header>

View File

@ -3,7 +3,7 @@ import {ref} from 'vue'
import { NAvatar } from 'naive-ui'
const defaultAvatar=ref(JSON.parse(localStorage.getItem('userInfo')).Avatar)
const defaultAvatar = ref(JSON.parse(localStorage.getItem('userInfo'))?.Avatar || '')
defineProps({
image: Boolean
@ -17,7 +17,9 @@ defineProps({
</template>
<span v-else class="text-[28px] dark:text-white">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" width="1em" height="1em">
<path d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z" fill="currentColor" />
<path
d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z"
fill="currentColor" />
</svg>
</span>
</template>

View File

@ -2,8 +2,8 @@
import {uploadFormData, uploadImg} from "@/api/api";
import {Local} from "@/utils/storage/storage";
import dayjs from "dayjs";
import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
import {computed, onMounted, onUnmounted, ref, watch,onBeforeMount} from 'vue'
import {useRouter} from 'vue-router'
import {AreaChartOutlined, PlusOutlined} from '@ant-design/icons-vue';
import html2canvas from 'html2canvas'
import {Message} from './components'
@ -23,6 +23,7 @@ import {sessionDetailForSetup} from '@/store'
import StopSvg from '@/assets/RecordStop12Regular.svg'
const sessionDetailData = sessionDetailForSetup()
let controller = new AbortController()
const {
sessionDetail: dataSources,
currentListUuid,
@ -31,7 +32,7 @@ const {
isGPT4,
loading
} = storeToRefs(sessionDetailData)
const router = useRouter()
const dialog = useDialog()
const ms = useMessage()
const chatStore = useChatStore()
@ -335,7 +336,10 @@ const footerClass = computed(() => {
})
const isShowBottom=ref(false)
onMounted(() => {
console.log(123)
if(!Local.get('token')){
router.push('/login')
}
scrollRef.value.addEventListener('scroll', function() {
if (scrollRef.value.scrollTop + scrollRef.value.clientHeight +100>= scrollRef.value.scrollHeight) {
isShowBottom.value=false

View File

@ -51,7 +51,7 @@ const show=ref(false)
<template>
<NScrollbar class="px-4">
<div class="flex flex-col gap-2 text-sm mb-1">
<template v-if="!dataList.length">
<template v-if="!dataList?.length">
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
<span>{{ $t('common.noData') }}</span>

View File

@ -75,6 +75,13 @@ const mobileSafeArea = computed(() => {
return {}
})
const options = () => {
if (!Local.get('ruleBtn')) {
return [{
label: 'GPT-3.5',
value: 'gpt-3.5-turbo',
permission: 'gpt-3.5-btn'
}]
}
return Local.get('ruleBtn').find(x => x === 'gpt-4-btn') ? [{
label: 'GPT-3.5',
value: 'gpt-3.5-turbo',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,232 @@
<script setup>
import { NButton, } from 'naive-ui'
import { useRouter, } from 'vue-router'
import { Local } from "@/utils/storage/storage";
import { loginApi, getCode, getMenuApi } from "@/api/api";
import { ref, onMounted,onUnmounted } from 'vue'
const router = useRouter()
const ms = useMessage()
//
const isCode = ref(false);
const username = ref('')
const password = ref('')
const code = ref('')
const checked = ref(false)
// CD
const leftTime = ref(60);
//
const canSend = ref(true);
const printTextWrod = ref(["FONCHAT", "Let's Chat", "Let's Create", "Let's Go"])
const backgroundColor = ref(['red', 'blue', '#f3f4f6', '#f3f4f6'])
let printInterval = null
onMounted(() => {
printText(printTextWrod.value[0])
let index = 1
printInterval = setInterval(() => {
if (index >= printTextWrod.value.length) {
index = 0
}
printText(printTextWrod.value[index])
index++
document.querySelector('#chat').innerText = ''
}, 2500)
})
onUnmounted(() => {
clearInterval(printInterval)
})
function login() {
router.push('/')
}
//
const changeLogin = () => {
password.value = '';
code.value = '';
isCode.value = !isCode.value;
};
//
const getCodeNum = async () => {
if (!username.value) {
ms.error('请输入手机号码')
return;
}
let data = {
TelNum: username.value
}
const res = await getCode(
data
)
if (res.status === 0) {
ms.success('验证码已发送,请注意查收')
//
canSend.value = false;
let timer = setInterval(() => {
leftTime.value--;
if (leftTime.value <= 0) {
canSend.value = true;
clearInterval(timer);
leftTime.value = 60;
}
}, 1000);
} else {
ms.error(res.msg)
canSend.value = true;
}
};
//
const goLogin = async () => {
if (!checked.value) {
ms.error('请先同意协议')
return;
}
if (!username.value) {
ms.error('请输入手机号码')
return;
}
if (!password.value && !isCode.value) {
ms.error('请输入密码')
return;
}
if (!code.value && isCode.value) {
ms.error('请输入验证码')
return;
}
let data = {
TelNum: username.value,
Password: password.value,
Code: code.value
}
const res = await loginApi(
data
)
if (res.status === 0) {
ms.success('登录成功')
Local.set("userInfo", res.data.AccountInfo);
Local.set("token", res.data.Token);
Local.set("RefreshToken", res.data.RefreshToken);
await getMenu();
} else {
ms.error(res.msg)
}
};
//
const getMenu = async () => {
const res = await getMenuApi({})
if (res.status === 0) {
let ruleBtn = [];
if (res.data.MyButtonAuths) {
ruleBtn = await res.data.MyButtonAuths.map((i) => {
return i.Url;
});
}
Local.set("ruleBtn", ruleBtn);
router.push('/')
} else {
ms.error(res.msg)
}
};
//
const goServeInfo = () => {
router.push('/serveInfo')
};
//
const goPrivateInfo = () => {
router.push('/privateInfo')
};
function printText(content, speed = 70) {
let dom = document.querySelector('#chat')
let index = 0
setCursorStatus(dom, 'typing')
let printInterval = setInterval(() => {
dom.innerText += content[index]
index++
if (index >= content.length) {
setCursorStatus(dom, 'end')
clearInterval(printInterval)
}
}, speed)
}
function setCursorStatus(dom, status) {
const classList = {
loading: 'typing blinker',
typing: 'typing',
end: '',
}
dom.className = classList[status]
}
</script>
<template>
<div class="flex h-full main-box">
<div class="px-4 m-auto space-y-4 max-[400px]">
<p style="padding: 10px;text-align: center;font-size: 26px;font-weight: bolder;" id="chat"></p>
<n-input round size="large" v-model:value="username" type="text" placeholder="请输入用户名" />
<n-input round size="large" v-if="!isCode" v-model:value="password" show-password-on="click" type="password"
placeholder="请输入密码" />
<n-input round size="large" v-if="isCode" v-model:value="code" type="text" placeholder="请输入验证码">
<template #suffix>
<div v-if="canSend" class="get-code" @click="getCodeNum">获取验证码</div>
<div v-else class="get-code">{{ leftTime }}s</div>
</template>
</n-input>
<div class="text-center">
<NButton size="large" circle type="primary" @click="goLogin" style="width: 250px;">
登录
</NButton>
<div v-if="!isCode" @click="changeLogin" style="margin-top: 20px; color: #7f71a5ff;cursor: pointer;">切换验证码登录
</div>
<div v-else @click="changeLogin" style="margin-top: 20px; color: #7f71a5ff;cursor: pointer;">切换账号密码登录</div>
<div class="check">
<n-checkbox v-model:checked="checked">
</n-checkbox>
<div class="txt">已阅读并同意
<text class="col" @click="goServeInfo">平台服务协议</text>
<text class="col" @click="goPrivateInfo">隐私权政策</text>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.main-box {
background-color: #f3f4f6;
}
.get-code {
color: #764cf6ff;
cursor: pointer;
user-select: none;
}
.check {
margin-top: 20px;
display: flex;
align-items: flex-start;
}
.txt {
font-size: 22rpx;
margin-left: 5px;
}
.col {
color: #5c9fff;
}
.typing::after {
content: '●';
}
.blinker::after {
animation: blinker 1s step-end infinite;
}
</style>

125
src/views/mine/index.vue Normal file
View File

@ -0,0 +1,125 @@
<script lang="ts" setup>
import { NButton } from 'naive-ui'
import { useRouter } from 'vue-router'
import { ref, onMounted, onUnmounted } from 'vue'
import { Local } from "@/utils/storage/storage";
const router = useRouter()
const ms = useMessage()
const startX = ref(0)
const startY = ref(0)
const endX = ref(0)
const endY = ref(0)
function goBack() {
router.go(-1)
}
function handleTouchStart(e) {
startX.value = e.touches[0].pageX
startY.value = e.touches[0].pageY
}
function handleTouchMove(e) {
endX.value = e.touches[0].pageX
endY.value = e.touches[0].pageY
if (endX.value - startX.value > 50 && Math.abs(endY.value - startY.value) < 50) {
goBack()
}
}
function showTips() {
ms.warning('请拨打客服电话18051299227进行注销')
}
function logOut() {
Local.clear()
router.push('/login')
}
onMounted(() => {
document.addEventListener('touchstart', handleTouchStart, false)
document.addEventListener('touchmove', handleTouchMove, false)
})
onUnmounted(() => {
document.removeEventListener('touchstart', handleTouchStart, false)
document.removeEventListener('touchmove', handleTouchMove, false)
})
//
const goServeInfo = () => {
router.push('/serveInfo')
};
//
const goPrivateInfo = () => {
router.push('/privateInfo')
};
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo')))
</script>
<template>
<div class="flex h-full">
<div class="header">
<n-page-header subtitle="" @back="goBack"></n-page-header>
</div>
<div class="w-full bg-white shadow-lg rounded-2xl dark:bg-gray-800 mt-40 ">
<div class="flex flex-col items-center justify-center p-4 -mt-16">
<a href="#" class="relative block">
<img alt="profil" :src="userInfo.Avatar" class="mx-auto object-cover rounded-full h-16 w-16 " />
</a>
<p class="mt-2 text-xl font-medium text-gray-800 dark:text-white">
{{ userInfo.NickName }}
</p>
<p class="flex items-center text-xs text-gray-400">
<svg width="10" height="10" fill="currentColor" class="w-4 h-4 mr-2" viewBox="0 0 1792 1792"
xmlns="http://www.w3.org/2000/svg">
<path
d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z">
</path>
</svg>
{{ userInfo.JobNum }}
</p>
<p class="text-xs text-gray-400">
{{ userInfo.DepartmentName }}
</p>
<div class="flex items-center justify-between w-full gap-4 mt-8">
<button type="button" style="background-color:#f0a020 ;" @click="logOut"
class="py-2 px-4 focus:ring-indigo-500 focus:ring-offset-indigo-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg ">
退出登录
</button>
</div>
<div class="flex items-center justify-between w-full gap-4 mt-8">
<button type="button" style="background-color: #FF0000;" @click="showTips"
class="py-2 px-4 focus:ring-indigo-500 focus:ring-offset-indigo-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg ">
注销账号
</button>
</div>
<div class="txt">
<text class="col" @click="goServeInfo">平台服务协议</text>
<text class="col" @click="goPrivateInfo">隐私权政策</text>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.header {
width: 100%;
position: fixed;
z-index: 999;
padding: 50px 20px 10px 20px;
border-bottom: 1px solid #f0f0f0;
}
.check {
margin-top: 20px;
display: flex;
align-items: flex-start;
}
.txt {
font-size: 22rpx;
margin-left: 5px;
margin-top: 50px;
}
.col {
color: #5c9fff;
}
</style>