Compare commits
No commits in common. "main" and "hanqingwei" have entirely different histories.
main
...
hanqingwei
@ -54,6 +54,5 @@ module.exports = {
|
|||||||
'scss/comment-no-empty': null,
|
'scss/comment-no-empty': null,
|
||||||
'selector-class-pattern': null,
|
'selector-class-pattern': null,
|
||||||
'font-family-no-missing-generic-family-keyword': null,
|
'font-family-no-missing-generic-family-keyword': null,
|
||||||
'declaration-property-value-no-unknown': null,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
14
.vscode/launch.json
vendored
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Debug h5",
|
|
||||||
"type": "chrome",
|
|
||||||
"runtimeArgs": ["--remote-debugging-port=9222"],
|
|
||||||
"request": "launch",
|
|
||||||
"url": "http://localhost:5173",
|
|
||||||
"webRoot": "${workspaceFolder}",
|
|
||||||
"preLaunchTask": "uni:h5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
16
.vscode/tasks.json
vendored
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "uni:h5",
|
|
||||||
"type": "npm",
|
|
||||||
"script": "dev --devtools",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": "$vite",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
BIN
AIchat.rar
Normal file
@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/feige996/unibest">
|
<a href="https://github.com/feige996/unibest">
|
||||||
<img width="160" src="./src/static/logo.png">
|
<img width="160" src="./src/static/logo.svg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
4
env/.env
vendored
@ -1,9 +1,9 @@
|
|||||||
VITE_APP_TITLE = 'aixiaomo'
|
VITE_APP_TITLE = 'unibest'
|
||||||
VITE_APP_PORT = 9000
|
VITE_APP_PORT = 9000
|
||||||
|
|
||||||
VITE_UNI_APPID = 'H57F2ACE4'
|
VITE_UNI_APPID = 'H57F2ACE4'
|
||||||
VITE_WX_APPID = 'wxa2abb91f64032a2b'
|
VITE_WX_APPID = 'wxa2abb91f64032a2b'
|
||||||
VITE_DEV_TOKEN= ""
|
VITE_DEV_TOKEN= "79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfc5ce974441a21f37c4a81e3853b862975bd76099c6e2158cb3681ca6497d2c159c3b954852ace961bc334cdd547d0b2b441fbf51f2ef339bb58c27181206e20b1eb4cf26398e43bba65eba121ad88f20b"
|
||||||
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
|
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
|
||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
|
|
||||||
|
7
env/.env.development
vendored
@ -1,11 +1,6 @@
|
|||||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
NODE_ENV = 'dev'
|
NODE_ENV = 'development'
|
||||||
# 是否去除console 和 debugger
|
# 是否去除console 和 debugger
|
||||||
VITE_DELETE_CONSOLE = false
|
VITE_DELETE_CONSOLE = false
|
||||||
# 是否开启sourcemap
|
# 是否开启sourcemap
|
||||||
VITE_SHOW_SOURCEMAP = true
|
VITE_SHOW_SOURCEMAP = true
|
||||||
|
|
||||||
# VITE_SERVER_BASEURL = 'http://114.218.158.24:9020'
|
|
||||||
VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn'
|
|
||||||
|
|
||||||
# VITE_SERVER_BASEURL = 'https://erptest.fontree.cn:9020'
|
|
6
env/.env.production
vendored
@ -1,10 +1,6 @@
|
|||||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
NODE_ENV = 'prod'
|
NODE_ENV = 'development'
|
||||||
# 是否去除console 和 debugger
|
# 是否去除console 和 debugger
|
||||||
VITE_DELETE_CONSOLE = true
|
VITE_DELETE_CONSOLE = true
|
||||||
# 是否开启sourcemap
|
# 是否开启sourcemap
|
||||||
VITE_SHOW_SOURCEMAP = false
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn' # 体制外 OA
|
|
||||||
|
|
||||||
|
|
||||||
|
3
env/.env.test
vendored
@ -1,5 +1,4 @@
|
|||||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
NODE_ENV = 'test'
|
NODE_ENV = 'development'
|
||||||
# 是否去除console 和 debugger
|
# 是否去除console 和 debugger
|
||||||
VITE_DELETE_CONSOLE = false
|
VITE_DELETE_CONSOLE = false
|
||||||
VITE_SERVER_BASEURL = 'http://114.218.158.24:9020'
|
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
"build:app-ios": "uni build -p app-ios",
|
"build:app-ios": "uni build -p app-ios",
|
||||||
"build:custom": "uni build -p",
|
"build:custom": "uni build -p",
|
||||||
"build:h5": "uni build",
|
"build:h5": "uni build",
|
||||||
"build:h5-dev": "uni build --mode dev",
|
|
||||||
"build": "uni build",
|
"build": "uni build",
|
||||||
"build:h5:ssr": "uni build --ssr",
|
"build:h5:ssr": "uni build --ssr",
|
||||||
"build:mp-alipay": "uni build -p mp-alipay",
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
3647
pnpm-lock.yaml
15
src/App.vue
@ -1,8 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
||||||
const { statusBarHeight } = useStatus()
|
|
||||||
import { useStatus } from '@/store/status'
|
|
||||||
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
console.log('App Launch')
|
console.log('App Launch')
|
||||||
@ -20,18 +18,7 @@ onHide(() => {
|
|||||||
button::after {
|
button::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
/*解决阅览图片关闭按钮会显示在状态栏区域的问题*/
|
|
||||||
#u-a-p > div > div {
|
|
||||||
margin-top: var(--statusBarHeight);
|
|
||||||
}
|
|
||||||
/*不显示滚动条的类*/
|
|
||||||
.no-scroll {
|
|
||||||
-ms-overflow-style: none; /* IE 和 Edge */
|
|
||||||
scrollbar-width: none; /* Firefox */
|
|
||||||
}
|
|
||||||
.no-scroll::-webkit-scrollbar {
|
|
||||||
display: none; /* Webkit 浏览器 */
|
|
||||||
}
|
|
||||||
swiper,
|
swiper,
|
||||||
scroll-view {
|
scroll-view {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -54,7 +54,7 @@ const httpInterceptor = {
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { token } = userStore.userInfo as unknown as IUserInfo
|
const { token } = userStore.userInfo as unknown as IUserInfo
|
||||||
if (token) {
|
if (token) {
|
||||||
options.header.Authorization = `${token}`
|
options.header.Authorization = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ import 'element-plus/dist/index.css'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors'
|
import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
// import VConsole from 'vconsole'
|
|
||||||
// new VConsole()
|
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "aixiaomo",
|
"name": "unibest",
|
||||||
"appid": "H57F2ACE4",
|
"appid": "H57F2ACE4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"versionName": "1.0.0",
|
"versionName": "1.0.0",
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"spacing": "3px",
|
"spacing": "3px",
|
||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
|
"__esModule": true,
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
@ -40,6 +41,14 @@
|
|||||||
"navigationBarTitleText": "关于"
|
"navigationBarTitleText": "关于"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/index/index1",
|
||||||
|
"type": "page",
|
||||||
|
"layout": "default",
|
||||||
|
"style": {
|
||||||
|
"navigationBarHidden": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/preview/index",
|
"path": "pages/preview/index",
|
||||||
"type": "page"
|
"type": "page"
|
||||||
|
553
src/pages/index/index1.vue
Normal file
@ -0,0 +1,553 @@
|
|||||||
|
<route lang="json5" type="page">
|
||||||
|
{
|
||||||
|
layout: 'default',
|
||||||
|
style: {
|
||||||
|
navigationBarHidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-screen bg-gray-50">
|
||||||
|
<!-- Navigation Bar -->
|
||||||
|
<div class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-10">
|
||||||
|
<image src="/static/aichat/back.png" class="w-2 h-4" @click="goBack" />
|
||||||
|
<div class="text-lg font-medium">小墨</div>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<image src="/static/aichat/time.png" class="w-5 h-5" @click="viewHistory" />
|
||||||
|
<image src="/static/aichat/new.png" class="w-5 h-5" @click="newChat" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 消息区 -->
|
||||||
|
<div :class="['flex relative', showActions ? 'h-105' : 'h-130']">
|
||||||
|
<!-- 背景层 -->
|
||||||
|
<div class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
|
||||||
|
<image src="/static/aichat/logo.png" class="w-20 h-24 mb-4" @click="newChat" />
|
||||||
|
<view class="text-xl font-medium mb-1">嗨! 我是小墨</view>
|
||||||
|
<view class="text-gray-400">开启新的聊天吧</view>
|
||||||
|
</div>
|
||||||
|
z
|
||||||
|
<div
|
||||||
|
ref="scrollEl"
|
||||||
|
class="flex-1 overflow-y-auto bg-gray-50"
|
||||||
|
:class="showActions ? 'pb-44' : 'pb-16'"
|
||||||
|
>
|
||||||
|
<div :class="['relative z-10 px-4 py-6', showActions ? 'mb--11 h-105' : 'mb--21 h-135']">
|
||||||
|
<template v-for="(msg, idx) in messages" :key="idx">
|
||||||
|
<view v-if="shouldShowTimestamp(idx)" class="text-center text-xs text-gray-500 my-2">
|
||||||
|
{{ formatDayGroup(msg.timestamp) }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex items-start"
|
||||||
|
:class="msg.role === 'assistant' ? 'justify-start' : 'justify-end'"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="msg.role === 'assistant'"
|
||||||
|
:src="assistantAvatar"
|
||||||
|
class="w-8 h-8 rounded-full mr-2 mt-1"
|
||||||
|
/>
|
||||||
|
<view class="relative max-w-[70%] mt-4 mb-3">
|
||||||
|
<view
|
||||||
|
:class="[
|
||||||
|
'absolute -top-4 text-xs text-gray-400',
|
||||||
|
msg.role === 'assistant' ? 'left-0' : 'right-0',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ formatTimeShort(msg.timestamp) }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
:class="[
|
||||||
|
'py-2 px-3 rounded-lg break-words mt-1',
|
||||||
|
msg.role === 'assistant'
|
||||||
|
? 'bg-[#f9f8fd] text-black shadow'
|
||||||
|
: 'bg-[#45299e] text-white',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ msg.content }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="msg.role === 'assistant' && msg.type === 'text'"
|
||||||
|
class="absolute bottom-0 flex space-x-3"
|
||||||
|
>
|
||||||
|
<image src="/static/aichat/copy.png" class="w-4 h-4" @click="copyText(msg)" />
|
||||||
|
<image
|
||||||
|
src="/static/aichat/resect.png"
|
||||||
|
class="w-4 h-4"
|
||||||
|
@click="refreshText(msg)"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
v-if="msg.role === 'user'"
|
||||||
|
:src="userAvatar"
|
||||||
|
class="w-8 h-8 rounded-full ml-2 mt-1"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部上传预览 + 输入区 -->
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300',
|
||||||
|
showActions ? 'h-45' : 'h-20',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 上传列表 -->
|
||||||
|
<div v-if="uploadList.length" class="flex px-4 py-2 overflow-x-auto space-x-3 bg-white">
|
||||||
|
<div
|
||||||
|
v-for="item in uploadList"
|
||||||
|
:key="item.id"
|
||||||
|
class="relative w-16 h-16 rounded overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- 预览图,成功后用后端返回的 URL;上传中可以先用本地预览 -->
|
||||||
|
<img
|
||||||
|
:src="item.url || item.localPath"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
@click="previewImage(item.url || item.localPath)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<div
|
||||||
|
class="absolute top-1 right-1 w-4 h-4 rounded-full bg-black bg-opacity-50 flex items-center justify-center cursor-pointer text-white text-xs"
|
||||||
|
@click="removeImage(item.id)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度 / 成功 / 失败 -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 left-0 w-full text-xs text-center text-white py-1"
|
||||||
|
:class="{
|
||||||
|
'bg-black bg-opacity-50': item.status === 'uploading',
|
||||||
|
'bg-green-600 bg-opacity-50': item.status === 'success',
|
||||||
|
'bg-red-600 bg-opacity-50': item.status === 'fail',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-if="item.status === 'uploading'">{{ item.progress }}%</template>
|
||||||
|
<template v-else-if="item.status === 'success'">✔ 成功</template>
|
||||||
|
<template v-else>
|
||||||
|
✖ 失败
|
||||||
|
<span class="cursor-pointer" @click.stop="retry(item)">↻</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 输入 + 切换 -->
|
||||||
|
<view class="flex items-center px-4 py-2.5 border-t border-solid border-[#E7E7E7]">
|
||||||
|
<input
|
||||||
|
v-model="inputText"
|
||||||
|
@keyup.enter="sendText"
|
||||||
|
placeholder="想对我说点什么~"
|
||||||
|
class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-full focus:outline-none"
|
||||||
|
/>
|
||||||
|
<image src="/static/aichat/add-circle.png" class="w-7 h-7 mx-3" @click="toggleActions" />
|
||||||
|
<image
|
||||||
|
src="/static/aichat/enter.png"
|
||||||
|
class="w-7 h-7"
|
||||||
|
@click="sendText"
|
||||||
|
:disabled="loading"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 操作面板 -->
|
||||||
|
<transition name="slide-up">
|
||||||
|
<view
|
||||||
|
v-show="showActions"
|
||||||
|
class="flex justify-around items-center h-20 border-t border-solid border-[#E7E7E7] bg-white"
|
||||||
|
>
|
||||||
|
<view class="flex flex-col items-center">
|
||||||
|
<image src="/static/aichat/phone-img.png" class="w-13 h-13" @click="onPickImage" />
|
||||||
|
<span class="text-xs mt-1 text-gray-500">照片</span>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center">
|
||||||
|
<image src="/static/aichat/photo.png" class="w-13 h-13" @click="onTakePhoto" />
|
||||||
|
<span class="text-xs mt-1 text-gray-500">拍摄</span>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center">
|
||||||
|
<image src="/static/aichat/files.png" class="w-13 h-13" @click="onPickFile" />
|
||||||
|
<span class="text-xs mt-1 text-gray-500">文件</span>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
1
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, nextTick } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { useUserStore } from '@/store'
|
||||||
|
import { getEnvBaseUrl } from '@/utils'
|
||||||
|
import type { IGptRequestBody } from '@/service/index/foo'
|
||||||
|
interface IUpload {
|
||||||
|
id: number
|
||||||
|
url: string
|
||||||
|
filePath: string
|
||||||
|
status: 'uploading' | 'success' | 'fail'
|
||||||
|
progress: number
|
||||||
|
detail: string
|
||||||
|
mask: string
|
||||||
|
}
|
||||||
|
interface IMessage {
|
||||||
|
role: 'user' | 'assistant'
|
||||||
|
type: 'text' | 'images'
|
||||||
|
content: string | string[]
|
||||||
|
timestamp: Date
|
||||||
|
}
|
||||||
|
interface UploadItem {
|
||||||
|
id: string
|
||||||
|
localPath: string // 本地临时路径,用于预览
|
||||||
|
url: string // 后端返回的在线 URL
|
||||||
|
status: 'uploading' | 'success' | 'fail'
|
||||||
|
progress: number // 上传进度 %
|
||||||
|
}
|
||||||
|
const assistantAvatar =
|
||||||
|
'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg'
|
||||||
|
const userAvatar = assistantAvatar
|
||||||
|
const baseUrl = getEnvBaseUrl()
|
||||||
|
const token = useUserStore().userInfo.token || import.meta.env.VITE_DEV_TOKEN || ''
|
||||||
|
const messages = reactive<IMessage[]>([])
|
||||||
|
const inputText = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const showActions = ref(false)
|
||||||
|
const scrollEl = ref<HTMLElement>()
|
||||||
|
const uploadList = reactive<IUpload[]>([])
|
||||||
|
const uploadId = ref(0)
|
||||||
|
|
||||||
|
function scrollToBottom() {
|
||||||
|
const el = scrollEl.value!
|
||||||
|
nextTick(() => {
|
||||||
|
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function addMessage(msg: IMessage) {
|
||||||
|
messages.push(msg)
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldShowTimestamp = (i: number) => {
|
||||||
|
if (i === 0) return true
|
||||||
|
return !dayjs(messages[i].timestamp).isSame(messages[i - 1].timestamp, 'day')
|
||||||
|
}
|
||||||
|
const formatDayGroup = (d: Date) => dayjs(d).format('YYYY/MM/DD HH:mm')
|
||||||
|
const formatTimeShort = (d: Date) => dayjs(d).format('MM/DD HH:mm')
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
function viewHistory() {
|
||||||
|
uni.navigateTo({ url: '/pages/history/history' })
|
||||||
|
}
|
||||||
|
function newChat() {
|
||||||
|
messages.splice(0)
|
||||||
|
inputText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleActions() {
|
||||||
|
showActions.value = !showActions.value
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相册
|
||||||
|
function onPickImage() {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 10,
|
||||||
|
success: (res: any) => {
|
||||||
|
res.tempFilePaths.forEach((path) => {
|
||||||
|
uploadId.value += 1
|
||||||
|
const id = uploadId.value
|
||||||
|
const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
|
||||||
|
uploadList.push(upload)
|
||||||
|
uploadFile(path, upload)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 拍照
|
||||||
|
function onTakePhoto() {
|
||||||
|
uni.chooseImage({
|
||||||
|
sourceType: ['camera'],
|
||||||
|
count: 1,
|
||||||
|
success: (res: any) => {
|
||||||
|
const path = res.tempFilePaths[0]
|
||||||
|
uploadId.value += 1
|
||||||
|
const id = uploadId.value
|
||||||
|
const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
|
||||||
|
uploadList.push(upload)
|
||||||
|
uploadFile(path, upload)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 文件
|
||||||
|
// 触发文件选择后,或拍照后,或其它入口都调用这个
|
||||||
|
function onPickFile(path: string, detail = '') {
|
||||||
|
const id = Date.now().toString()
|
||||||
|
const item: UploadItem = {
|
||||||
|
id,
|
||||||
|
localPath: path,
|
||||||
|
url: '',
|
||||||
|
status: 'uploading',
|
||||||
|
progress: 0,
|
||||||
|
}
|
||||||
|
uploadList.push(item)
|
||||||
|
startUpload(item, detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadFile {
|
||||||
|
uid: string
|
||||||
|
name: string
|
||||||
|
size: number
|
||||||
|
progress: number // 上传进度 0-100
|
||||||
|
status: 'uploading' | 'success' | 'error'
|
||||||
|
file: File
|
||||||
|
url?: string // 本地预览URL或服务器返回的URL
|
||||||
|
}
|
||||||
|
const filesList = ref<UploadFile[]>([])
|
||||||
|
async function startUpload(item: UploadItem, detail: string) {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 标记开始上传
|
||||||
|
item.status = 'uploading'
|
||||||
|
item.progress = 0
|
||||||
|
|
||||||
|
// 发起上传 (不带 success/fail 回调)
|
||||||
|
// @ts-ignore: uni.uploadFile 返回 UploadTask 兼具 Promise 接口
|
||||||
|
const uploadTask: UniApp.UploadTask & Promise<UniApp.UploadFileRes> = uni.uploadFile({
|
||||||
|
// url: 'http://114.218.158.24:9020/upload/multi',
|
||||||
|
url: 'https://ukw0y1.laf.run/upload',
|
||||||
|
filePath: item.localPath,
|
||||||
|
name: 'k1', // 这里的 name 依然是 formData 里的字段名,后端会以此为 key
|
||||||
|
formData: {
|
||||||
|
source: 'chat',
|
||||||
|
mask: '2076',
|
||||||
|
detail,
|
||||||
|
type: 'image',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听进度
|
||||||
|
uploadTask.onProgressUpdate((res: { progress: number }) => {
|
||||||
|
item.progress = res.progress
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3s 超时自动 abort
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
if (item.status === 'uploading') {
|
||||||
|
uploadTask.abort()
|
||||||
|
item.status = 'fail'
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 等待上传完成
|
||||||
|
const res = await uploadTask
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
// 解析后端 JSON
|
||||||
|
const resp = JSON.parse(res.data) as {
|
||||||
|
code: number
|
||||||
|
data: Record<string, string>
|
||||||
|
}
|
||||||
|
console.log(resp, 'resp')
|
||||||
|
if (resp.code === 0 && resp.data) {
|
||||||
|
// 遍历 data 对象,找第一个非空值
|
||||||
|
const urls = Object.values(resp.data).filter((u) => !!u)
|
||||||
|
if (urls.length > 0) {
|
||||||
|
item.url = urls[0]
|
||||||
|
item.status = 'success'
|
||||||
|
} else {
|
||||||
|
console.warn('没有拿到任何上传后的 URL')
|
||||||
|
item.status = 'fail'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('后端返回异常 code=', resp.code)
|
||||||
|
item.status = 'fail'
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
console.error('uploadFile 出错:', err)
|
||||||
|
item.status = 'fail'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 点击重试
|
||||||
|
function retry(item: UploadItem) {
|
||||||
|
item.status = 'uploading'
|
||||||
|
item.progress = 0
|
||||||
|
startUpload(item, '') // 如果需要 detail,可缓存后传入
|
||||||
|
}
|
||||||
|
// 删除
|
||||||
|
function removeImage(id: string) {
|
||||||
|
const idx = uploadList.findIndex((i) => i.id === id)
|
||||||
|
if (idx >= 0) uploadList.splice(idx, 1)
|
||||||
|
}
|
||||||
|
// 预览(可自行实现 uni.previewImage 等)
|
||||||
|
function previewImage(url: string) {
|
||||||
|
uni.previewImage({ urls: [url] })
|
||||||
|
}
|
||||||
|
// 上传使用 postUpload
|
||||||
|
async function uploadFile(path: string, upload: IUpload) {
|
||||||
|
// 标记开始
|
||||||
|
upload.status = 'uploading'
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-ignore uni.uploadFile 返回 Promise
|
||||||
|
const res: UniApp.UploadFileRes = await uni.uploadFile({
|
||||||
|
// url: 'http://114.218.158.24:9020/upload/multi',
|
||||||
|
url: 'https://ukw0y1.laf.run/upload',
|
||||||
|
filePath: path,
|
||||||
|
name: 'file', // 这个 name 依然是 formData key,后端会把它用在 data.data 对象里
|
||||||
|
formData: {
|
||||||
|
source: 'chat',
|
||||||
|
mask: '2076',
|
||||||
|
type: 'image',
|
||||||
|
k1: 'xxxx.png',
|
||||||
|
k2: 'xxxx.png',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 解析后端 JSON
|
||||||
|
const resp = JSON.parse(res.data) as {
|
||||||
|
code: number
|
||||||
|
data: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.code === 0 && resp.data) {
|
||||||
|
// 找到 data 对象里第一个有值的字段并赋给 upload.url
|
||||||
|
let found = false
|
||||||
|
for (const key in resp.data) {
|
||||||
|
const url = resp.data[key]
|
||||||
|
if (url) {
|
||||||
|
upload.url = url
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
upload.status = 'success'
|
||||||
|
} else {
|
||||||
|
console.warn('没有取到任何上传后的 URL')
|
||||||
|
upload.status = 'fail'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('后端返回异常 code=', resp.code)
|
||||||
|
upload.status = 'fail'
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('uploadFile 出错:', err)
|
||||||
|
upload.status = 'fail'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function sendText() {
|
||||||
|
const text = inputText.value.trim()
|
||||||
|
if (!text || loading.value) return
|
||||||
|
|
||||||
|
addMessage({ role: 'user', type: 'text', content: text, timestamp: new Date() })
|
||||||
|
inputText.value = ''
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const aiMsg: IMessage = {
|
||||||
|
role: 'assistant',
|
||||||
|
type: 'text',
|
||||||
|
content: '',
|
||||||
|
timestamp: new Date(),
|
||||||
|
}
|
||||||
|
addMessage(aiMsg)
|
||||||
|
|
||||||
|
const body: IGptRequestBody = {
|
||||||
|
model: 'gpt-4-vision-preview',
|
||||||
|
max_tokens: 1000,
|
||||||
|
temperature: 1,
|
||||||
|
top_p: 1,
|
||||||
|
presence_penalty: 0,
|
||||||
|
frequency_penalty: 0,
|
||||||
|
messages: [{ role: 'user', content: [{ type: 'text', text }] }],
|
||||||
|
stream: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(baseUrl + '/chat/completion', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Authorization: token },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
const reader = resp.body!.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ''
|
||||||
|
let done = false
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
const { value, done: streamDone } = await reader.read()
|
||||||
|
done = streamDone
|
||||||
|
if (value) {
|
||||||
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
const parts = buffer.split('data: ')
|
||||||
|
buffer = parts.pop()!
|
||||||
|
for (const part of parts) {
|
||||||
|
scrollToBottom()
|
||||||
|
console.log('1')
|
||||||
|
const chunk = part.trim()
|
||||||
|
if (chunk === '[DONE]') {
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(chunk)
|
||||||
|
const delta = json.choices?.[0]?.delta?.content
|
||||||
|
if (delta) {
|
||||||
|
aiMsg.content += delta
|
||||||
|
scrollToBottom()
|
||||||
|
console.log('2')
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scrollToBottom()
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
showActions.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyText(msg: IMessage) {
|
||||||
|
if (typeof msg.content === 'string') {
|
||||||
|
navigator.clipboard.writeText(msg.content)
|
||||||
|
alert('已复制')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function refreshText(msg: IMessage) {
|
||||||
|
if (typeof msg.content === 'string') {
|
||||||
|
inputText.value = msg.content
|
||||||
|
const idx = messages.indexOf(msg)
|
||||||
|
messages.splice(idx, 1)
|
||||||
|
sendText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
.slide-up-enter-to,
|
||||||
|
.slide-up-leave-from {
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
.font-pf {
|
||||||
|
font-family: PingFangSC-Medium, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,50 +0,0 @@
|
|||||||
import { getEnvBaseUrl } from '@/utils'
|
|
||||||
import { httpPost } from '@/utils/http'
|
|
||||||
// import { TOKEN } from './test';
|
|
||||||
import { apis, uploadFile } from '@/utils/tools'
|
|
||||||
const baseUrl = getEnvBaseUrl()
|
|
||||||
|
|
||||||
// /artwork/get-chunk-list 获取文件分断数据
|
|
||||||
// /artwork/upload-chunk 分断上传画作图片
|
|
||||||
export const uploadFiles = (url, params) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.uploadFile({
|
|
||||||
url: baseUrl + url,
|
|
||||||
filePath: params.tempFilePath,
|
|
||||||
name: 'Chunk',
|
|
||||||
formData: params.formData,
|
|
||||||
header: {
|
|
||||||
},
|
|
||||||
complete: (res) => {
|
|
||||||
// console.log('res: ',res);
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
resolve(res)
|
|
||||||
} else {
|
|
||||||
reject(res)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const getChunkList = (file) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: baseUrl + '/artwork/get-chunk-list',
|
|
||||||
data: file,
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json', //"multipart/form-data"
|
|
||||||
},
|
|
||||||
success(res) {
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
fail(rej) {
|
|
||||||
reject(Jrej)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const uploadFileChunk = (data) => {
|
|
||||||
let token = uni.getStorageSync('authorization')
|
|
||||||
return uploadFiles('/artwork/upload-chunk', data)
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
// 发送消息
|
|
||||||
async function sendText1(msgData = '') {
|
|
||||||
// let msgData=''
|
|
||||||
// if(msg===''){
|
|
||||||
// msgData=msg
|
|
||||||
// }else if(msg.detail && msg.detail.value){
|
|
||||||
|
|
||||||
// msgData=msg.detail.value
|
|
||||||
// }
|
|
||||||
// console.log('msgData',msg)
|
|
||||||
// uni.showToast({ title: inputText.value, icon: 'error' })
|
|
||||||
|
|
||||||
msgLoading.value = true
|
|
||||||
const text = inputText.value.trim()
|
|
||||||
const dataBlo = text //toRaw(msgData)
|
|
||||||
console.log('dataBlo', dataBlo)
|
|
||||||
if (!text) {
|
|
||||||
msgLoading.value = false
|
|
||||||
|
|
||||||
uni.showToast({
|
|
||||||
title: '请输入信息',
|
|
||||||
icon: 'error'
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (loading.value) return
|
|
||||||
|
|
||||||
inputText.value = ''
|
|
||||||
loading.value = true
|
|
||||||
const tempUploadList = Object.assign([], uploadList)
|
|
||||||
let fileList: any[] = []
|
|
||||||
if (tempUploadList.length > 0) {
|
|
||||||
fileList = tempUploadList.map((item: UploadFile) => {
|
|
||||||
if (item.uploadFileType === uploadFileTypeEm.image) {
|
|
||||||
// 图片格式
|
|
||||||
return {
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: item.url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else if (item.uploadFileType === uploadFileTypeEm.video) {
|
|
||||||
console.log(item, '====item=====')
|
|
||||||
// 视频格式(改成和图片一样的结构)
|
|
||||||
return {
|
|
||||||
type: 'video_url',
|
|
||||||
video_url: {
|
|
||||||
url: item.ori_url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 其他文件类型保持原样
|
|
||||||
const fileType = `${item.uploadFileType}_url`
|
|
||||||
return {
|
|
||||||
type: fileType,
|
|
||||||
[fileType]: item.url,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加用户文本消息
|
|
||||||
addMessage({
|
|
||||||
role: 'user',
|
|
||||||
type: 'text',
|
|
||||||
content: text,
|
|
||||||
timestamp: new Date(),
|
|
||||||
})
|
|
||||||
|
|
||||||
//图片、视频、文件分开发送
|
|
||||||
if (tempUploadList.length > 0) {
|
|
||||||
Object.values(uploadFileTypeEm).forEach((item: any) => {
|
|
||||||
if (tempUploadList.find((v: UploadFile) => v.uploadFileType === item)) {
|
|
||||||
addMessage({
|
|
||||||
role: 'user',
|
|
||||||
type: item,
|
|
||||||
content: tempUploadList.filter((v) => v.uploadFileType === item),
|
|
||||||
timestamp: new Date(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//更新上下文消息
|
|
||||||
historyUserMsgs.push({
|
|
||||||
role: 'user',
|
|
||||||
content: [{
|
|
||||||
type: 'text',
|
|
||||||
text
|
|
||||||
}, ...fileList],
|
|
||||||
timestamp: new Date(),
|
|
||||||
})
|
|
||||||
|
|
||||||
//显示更多图片遮罩
|
|
||||||
const showMoreImgMask = tempUploadList.filter((v: UploadFile) => v.uploadFileType === uploadFileTypeEm.image).length > 4
|
|
||||||
showImageMask.value = showMoreImgMask
|
|
||||||
} else {
|
|
||||||
//更新上下文消息
|
|
||||||
historyUserMsgs.push({
|
|
||||||
role: 'user',
|
|
||||||
content: text,
|
|
||||||
timestamp: new Date(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI消息
|
|
||||||
// const aiMsg: IMessage = {
|
|
||||||
const aiMsg = {
|
|
||||||
role: 'assistant',
|
|
||||||
type: 'text',
|
|
||||||
content: inputText.value,
|
|
||||||
timestamp: new Date(),
|
|
||||||
}
|
|
||||||
addMessage(aiMsg)
|
|
||||||
|
|
||||||
//清除上传列表
|
|
||||||
uploadList.splice(0, uploadList.length)
|
|
||||||
// console.log('[msgData] : historyUserMsgs: ',msgData);
|
|
||||||
// console.log('[msgData] : historyUserMsgs: ', historyUserMsgs);
|
|
||||||
const body: IGptRequestBody = {
|
|
||||||
model: chatMode.value,
|
|
||||||
max_tokens: 1000,
|
|
||||||
temperature: 1,
|
|
||||||
listUuid: listUuid.value,
|
|
||||||
top_p: 1,
|
|
||||||
presence_penalty: 0,
|
|
||||||
frequency_penalty: 0,
|
|
||||||
messages: historyUserMsgs, // text ? [aiMsg] : historyUserMsgs,
|
|
||||||
stream: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// aiMsg.content = ''
|
|
||||||
// 发送问题到后端
|
|
||||||
const resp = await fetch(baseUrl + '/chat/app/completion', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: token.value
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
|
|
||||||
const reader = resp.body!.getReader()
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
let buffer = ''
|
|
||||||
let done = false
|
|
||||||
|
|
||||||
while (!done) {
|
|
||||||
const {
|
|
||||||
value,
|
|
||||||
done: streamDone
|
|
||||||
} = await reader.read()
|
|
||||||
done = streamDone
|
|
||||||
if (value) {
|
|
||||||
buffer += decoder.decode(value, {
|
|
||||||
stream: true
|
|
||||||
})
|
|
||||||
const lines = buffer.split(/\r?\n/)
|
|
||||||
buffer = lines.pop() !
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
// 只处理以 "data: " 开头的行
|
|
||||||
if (!line.startsWith('data: ')) continue
|
|
||||||
const chunk = line.slice(6).trim()
|
|
||||||
|
|
||||||
if (chunk === '[DONE]') {
|
|
||||||
done = true
|
|
||||||
console.log('sss')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(chunk)
|
|
||||||
const delta = json.choices?.[0]?.delta?.content
|
|
||||||
|
|
||||||
if (delta) {
|
|
||||||
msgLoading.value = false
|
|
||||||
aiMsg.content += delta
|
|
||||||
//每次更新messages消息,实现流式输出
|
|
||||||
messages[messages.length - 1] = {
|
|
||||||
...aiMsg
|
|
||||||
}
|
|
||||||
scrollToBottom()
|
|
||||||
console.log('2')
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
//更新上下文消息
|
|
||||||
done && historyUserMsgs.push(aiMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scrollToBottom()
|
|
||||||
} catch (err) {
|
|
||||||
aiMsg.content = '请重新发送'
|
|
||||||
//更新messages消息
|
|
||||||
messages[messages.length - 1] = {
|
|
||||||
...aiMsg
|
|
||||||
}
|
|
||||||
console.error(err)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
showActions.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TOKEN="79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfcde73053dc1f269841c964c3b0779ceae38fcd1ac41220de5941cafd00664ae15bb706dfecc00972d1cf3c94b3ddec7758e514d8c0b32e2195e3bcb802d58861ca93e98cf322b9824623cfba4820be34e"
|
|
@ -1,183 +0,0 @@
|
|||||||
export const fileSuffix = (str) => {
|
|
||||||
if (!str) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
let reg = /\.\w*$/
|
|
||||||
return str.match(reg)[0]
|
|
||||||
}
|
|
||||||
export const isJsonObject = (json) => {
|
|
||||||
const str = json.trim()
|
|
||||||
return str.startsWith('{') && str.endsWith('}')
|
|
||||||
}
|
|
||||||
export const officeFileTypeList = ['.docx', '.doc', '.xls', '.xlsx', '.pdf', '.txt']
|
|
||||||
export const videoFileType = ['.mp4', '.mov', '.wmv']
|
|
||||||
export const picFileType = ['.jpg', '.png', '.jpeg']
|
|
||||||
export const formatParams = (uploadList) => {
|
|
||||||
// 上传文件formData类型
|
|
||||||
|
|
||||||
let photoList = [] // 媒体文件 参数中的content
|
|
||||||
let videoList = []
|
|
||||||
let fileList = [] // 文档文件
|
|
||||||
uploadList.forEach((item) => {
|
|
||||||
if (picFileType.includes(fileSuffix(item.ori_url))) {
|
|
||||||
// 图片
|
|
||||||
let media = {
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: item.ori_url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
photoList.push(media)
|
|
||||||
} else if (videoFileType.includes(fileSuffix(item.ori_url))) {
|
|
||||||
// 视频
|
|
||||||
let media = {
|
|
||||||
type: 'video_url',
|
|
||||||
video_url: {
|
|
||||||
url: item.ori_url,
|
|
||||||
poster: item.url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
videoList.push(media)
|
|
||||||
} else if (officeFileTypeList.includes(fileSuffix(item.ori_url))) {
|
|
||||||
const config = {
|
|
||||||
content: item.url,
|
|
||||||
filename: item.name,
|
|
||||||
title: item.name,
|
|
||||||
}
|
|
||||||
let file = {
|
|
||||||
role: 'system',
|
|
||||||
name: item.name,
|
|
||||||
// content: item.url,
|
|
||||||
content: {}, // item.url,
|
|
||||||
size: item.size,
|
|
||||||
mask: 'new',
|
|
||||||
}
|
|
||||||
fileList.push(file)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return { photoList: photoList, videoList: videoList, file: fileList }
|
|
||||||
}
|
|
||||||
export const calcFileSize = (size: number) => {
|
|
||||||
const type = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
||||||
// for(let i=0;i<type.length && size>1024;i++){
|
|
||||||
// size/=1024
|
|
||||||
// }
|
|
||||||
let unit = 'B'
|
|
||||||
while (size > 1024) {
|
|
||||||
size /= 1024
|
|
||||||
unit = type.shift()
|
|
||||||
}
|
|
||||||
return `${Math.ceil(size)}${unit}`
|
|
||||||
}
|
|
||||||
// 格式化请求数据,从页面渲染的数据改为请求数据
|
|
||||||
export function formatData(list) {
|
|
||||||
let result = []
|
|
||||||
let msg = null
|
|
||||||
list.forEach((item, index) => {
|
|
||||||
// if (index === list.length - 1) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (item.type === 'text') {
|
|
||||||
result.push({
|
|
||||||
role: item.role,
|
|
||||||
content: item.content,
|
|
||||||
type: item.type,
|
|
||||||
mask: item.mask,
|
|
||||||
})
|
|
||||||
if (item.role !== 'assistant') {
|
|
||||||
msg = {
|
|
||||||
role: item.role,
|
|
||||||
content: item.content,
|
|
||||||
type: item.type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (item.type === 'image' || item.type === 'video') {
|
|
||||||
// 图片与视频混合在一起
|
|
||||||
const content = []
|
|
||||||
item.content.forEach((child) => {
|
|
||||||
if (child.type === 'image_url') {
|
|
||||||
content.push({
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: child.image_url.url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if (child.type === 'video_url') {
|
|
||||||
content.push({
|
|
||||||
type: 'video_url',
|
|
||||||
video_url: {
|
|
||||||
url: child.video_url.url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (msg) {
|
|
||||||
content.push({
|
|
||||||
type: 'text',
|
|
||||||
text: msg.content,
|
|
||||||
})
|
|
||||||
msg = null
|
|
||||||
}
|
|
||||||
result.push({
|
|
||||||
role: 'user',
|
|
||||||
content: content,
|
|
||||||
type: item.type,
|
|
||||||
mask: item.mask,
|
|
||||||
})
|
|
||||||
} else if (item.type === 'file') {
|
|
||||||
let content = []
|
|
||||||
item.content.forEach((child) => {
|
|
||||||
if (child.role === 'system') {
|
|
||||||
content.push({
|
|
||||||
role: 'system',
|
|
||||||
content: child.content,
|
|
||||||
type: 'file',
|
|
||||||
mask: item.mask,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log(child)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (msg) {
|
|
||||||
content.push(msg)
|
|
||||||
msg = null
|
|
||||||
}
|
|
||||||
result = result.concat(content)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function sliceFile(blob: Blob, chunkSize: number): Blob[] {
|
|
||||||
const chunks: Blob[] = []
|
|
||||||
let cursor = 0
|
|
||||||
while (cursor < blob.size) {
|
|
||||||
chunks.push(blob.slice(cursor, cursor + chunkSize))
|
|
||||||
cursor += chunkSize
|
|
||||||
}
|
|
||||||
return chunks
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 对 Base64 字符串进行切片
|
|
||||||
*/
|
|
||||||
export function sliceBase64(base64: string, chunkSize: number = 1024 * 1024): string[] {
|
|
||||||
// 提取实际 Base64 数据部分
|
|
||||||
// const base64Data = base64.split(',')[1]
|
|
||||||
const byteStringLength = base64.length
|
|
||||||
const slices = []
|
|
||||||
console.log(base64)
|
|
||||||
|
|
||||||
for (let i = 0; i < byteStringLength; i += chunkSize) {
|
|
||||||
const chunk = base64.slice(i, i + chunkSize)
|
|
||||||
slices.push(chunk) // 可选:保留前缀
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readFile(file, chunkSize = 10 * 1024 * 1024) {
|
|
||||||
const blob = await fetch(file.tempFilePath)
|
|
||||||
const buffer = await blob.blob()
|
|
||||||
return sliceFile(buffer, chunkSize)
|
|
||||||
}
|
|
@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="flex flex-col h-90% bg-#ffffff">
|
<view class="image-gallery grid grid-cols-4 gap-1 p-4" v-if="imageList.length > 0">
|
||||||
<view
|
|
||||||
class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border"
|
|
||||||
>
|
|
||||||
<image src="/static/aichat/black.png" class="w-3 h-4 mt-1" @click="goBack" />
|
|
||||||
</view>
|
|
||||||
<view class="image-gallery grid grid-cols-4 gap-1 p-4 mt-20" v-if="imageList.length > 0">
|
|
||||||
<view
|
<view
|
||||||
v-for="(image, index) in imageList"
|
v-for="(image, index) in imageList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@ -17,39 +11,14 @@
|
|||||||
class="w-full h-full"
|
class="w-full h-full"
|
||||||
@click="handleImageClick(index)"
|
@click="handleImageClick(index)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- <wd-img :width="100" :height="100" :src="image.url" :enable-preview="true" /> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 自定义预览弹窗 -->
|
<view v-if="videoList.length > 0" class="flex items-center justify-center">
|
||||||
<view
|
|
||||||
v-if="showPreview"
|
|
||||||
class="fixed inset-0 bg-black bg-opacity-90 z-999 flex justify-center items-start pt-10"
|
|
||||||
@click="closePreview"
|
|
||||||
>
|
|
||||||
<view class="relative w-full max-w-full mt-8 p-5 box-border">
|
|
||||||
<image
|
|
||||||
class="w-full max-h-[calc(100vh-90px)] object-contain"
|
|
||||||
:src="previewUrl"
|
|
||||||
mode="widthFix"
|
|
||||||
@click.stop
|
|
||||||
/>
|
|
||||||
<view
|
|
||||||
class="absolute top--8 right-5 text-white text-3xl z-1000 w-10 h-10 text-center leading-10"
|
|
||||||
@click.stop="closePreview"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="videoList.length > 0" class="flex items-center justify-center mt-30">
|
|
||||||
<view v-for="(video, index) in videoList" :key="index" class="w-full h-50">
|
<view v-for="(video, index) in videoList" :key="index" class="w-full h-50">
|
||||||
<video :src="video" class="w-full h-full" controls :poster="video.url"></video>
|
<video :src="video" class="w-full h-full" controls :poster="video.url"></video>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -61,12 +30,8 @@ const imageList = ref([])
|
|||||||
const videoList = ref([])
|
const videoList = ref([])
|
||||||
|
|
||||||
// 图片点击处理
|
// 图片点击处理
|
||||||
// const handleImageClick = (index) => {
|
const handleImageClick = (index) => {
|
||||||
// uni.previewImage({ urls: [imageList.value[index].url] })
|
uni.previewImage({ urls: [imageList.value[index].url] })
|
||||||
// }
|
|
||||||
// 返回上一个页面
|
|
||||||
const goBack = () => {
|
|
||||||
uni.navigateBack({ delta: 1 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
@ -84,18 +49,6 @@ onMounted(() => {
|
|||||||
videoList.value = previewVideos
|
videoList.value = previewVideos
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const showPreview = ref(false)
|
|
||||||
const previewUrl = ref('')
|
|
||||||
const previewTop = ref(30) // 距离顶部30px
|
|
||||||
const handleImageClick = (index) => {
|
|
||||||
previewUrl.value = imageList.value[index].url
|
|
||||||
showPreview.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const closePreview = () => {
|
|
||||||
showPreview.value = false
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<web-view :src="link" :webview-styles="webviewStyles"></web-view>
|
<web-view :src="link"></web-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -7,12 +7,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
link: '',
|
link: '',
|
||||||
webviewStyles: {
|
|
||||||
width: '100%',
|
|
||||||
progress: {
|
|
||||||
color: '#999',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
@ -20,39 +14,6 @@ export default {
|
|||||||
this.link = options.link
|
this.link = options.link
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
// 只有 Webview 环境才有 plus
|
|
||||||
// if (window.plus) {
|
|
||||||
// const wv = plus.webview.currentWebview()
|
|
||||||
// // 页面加载完后再注入 padding
|
|
||||||
// wv.addEventListener('loaded', () => {
|
|
||||||
// wv.evalJS(`
|
|
||||||
// document.documentElement.style.padding = '30px';
|
|
||||||
// document.body.style.margin = '0';
|
|
||||||
// `)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
uni.getSystemInfo({
|
|
||||||
success: (res) => {
|
|
||||||
// 获取状态栏高度和窗口高度
|
|
||||||
const statusBarHeight = res.statusBarHeight
|
|
||||||
const windowHeight = res.windowHeight
|
|
||||||
|
|
||||||
// 计算WebView高度(窗口高度减去状态栏高度)
|
|
||||||
this.webviewStyles.height = windowHeight - statusBarHeight
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('获取系统信息失败:', err)
|
|
||||||
// 默认使用窗口高度
|
|
||||||
uni.getSystemInfo({
|
|
||||||
success: (res) => {
|
|
||||||
this.webviewStyles.height = res.windowHeight
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ export interface IGptRequestBody {
|
|||||||
max_tokens: number
|
max_tokens: number
|
||||||
temperature: number
|
temperature: number
|
||||||
top_p: number
|
top_p: number
|
||||||
listUuid: string
|
|
||||||
presence_penalty: number
|
presence_penalty: number
|
||||||
frequency_penalty: number
|
frequency_penalty: number
|
||||||
messages: IMessage[]
|
messages: IMessage[]
|
||||||
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 827 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -1,15 +0,0 @@
|
|||||||
import {ref} from 'vue'
|
|
||||||
import {createGlobalState, useStorage} from "@vueuse/core";
|
|
||||||
import {uniStorage} from "@/utils/uniStorage";
|
|
||||||
export const useStatus =createGlobalState(()=>{
|
|
||||||
const currentNavbar=ref({title:'',url:''})
|
|
||||||
const applyTabbarIndex=ref(0)
|
|
||||||
const statusBarHeight = ref(window?.plus?.navigator?.getStatusbarHeight() ?? 0)
|
|
||||||
const tabBarIndex = useStorage('tabBarIndex', 0, uniStorage)
|
|
||||||
return {
|
|
||||||
statusBarHeight,
|
|
||||||
applyTabbarIndex,
|
|
||||||
currentNavbar,
|
|
||||||
tabBarIndex
|
|
||||||
}
|
|
||||||
})
|
|
1
src/types/uni-pages.d.ts
vendored
@ -6,6 +6,7 @@
|
|||||||
interface NavigateToOptions {
|
interface NavigateToOptions {
|
||||||
url: "/pages/index/index" |
|
url: "/pages/index/index" |
|
||||||
"/pages/about/about" |
|
"/pages/about/about" |
|
||||||
|
"/pages/index/index1" |
|
||||||
"/pages/preview/index" |
|
"/pages/preview/index" |
|
||||||
"/pages/webview/index";
|
"/pages/webview/index";
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import {httpPost,httpGet} from "./http"
|
|
||||||
import {getEnvBaseUrl} from "./index"
|
|
||||||
|
|
||||||
const baseUrl=getEnvBaseUrl();
|
|
||||||
|
|
||||||
// 发送文本消息
|
|
||||||
const endMsg=async (params)=>{
|
|
||||||
return await httpGet(baseUrl+url,data)
|
|
||||||
}
|
|
@ -1,281 +0,0 @@
|
|||||||
import { getEnvBaseUrl } from "./index";
|
|
||||||
export const baseUrl =getEnvBaseUrl();
|
|
||||||
export const api = (url = '', params = {}, method = 'post') => {
|
|
||||||
// let authorization = uni.getStorageSync('authorization');
|
|
||||||
// console.log(authorization);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url:baseUrl+url,
|
|
||||||
data: params,
|
|
||||||
method,
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
//'authorization': authorization,
|
|
||||||
},
|
|
||||||
complete: (res) => {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
resolve(res.data);
|
|
||||||
} else {
|
|
||||||
reject(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const api_form = (url = '', params = {}, method = 'post') => {
|
|
||||||
let authorization = uni.getStorageSync('authorization');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: baseUrl + url,
|
|
||||||
data: params,
|
|
||||||
method,
|
|
||||||
header: {
|
|
||||||
// 'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'authorization': authorization,
|
|
||||||
},
|
|
||||||
complete: (res) => {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
resolve(res.data);
|
|
||||||
} else {
|
|
||||||
reject(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 接口获取
|
|
||||||
export const apis = (url = '', params = {}, method = 'post') => {
|
|
||||||
let authorization = uni.getStorageSync('authorization');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: baseUrl + url,
|
|
||||||
data: params,
|
|
||||||
method,
|
|
||||||
header: {
|
|
||||||
// 'Content-Type':'multipart/form-data',//application/x-www-form-urlencoded',
|
|
||||||
'authorization': authorization,
|
|
||||||
},
|
|
||||||
complete: (res) => {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
resolve(res.data);
|
|
||||||
} else {
|
|
||||||
reject(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 错误toast提示
|
|
||||||
export const showToastErr = (title) => {
|
|
||||||
uni.showToast({
|
|
||||||
title: title,
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000,
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 成功toast提示
|
|
||||||
export const showToastOk = (title) => {
|
|
||||||
uni.showToast({
|
|
||||||
title: title,
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000,
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export const showToastOkMask = (title) => {
|
|
||||||
uni.showToast({
|
|
||||||
title: title,
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000,
|
|
||||||
mask: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export const showloading=(title)=>{
|
|
||||||
uni.showLoading({
|
|
||||||
mask:true,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export const hideloading=()=>{
|
|
||||||
uni.hideLoading();
|
|
||||||
}
|
|
||||||
export const navigateTo = (url) => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: url
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export const redirectTo = (url) => {
|
|
||||||
uni.redirectTo({
|
|
||||||
url: url
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export const reLaunch = (url) => {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: url
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 上传图片(选择图片)
|
|
||||||
export const selectPic = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 9, //默认9
|
|
||||||
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
|
|
||||||
sourceType: ["album", "camera"], //从相册选择
|
|
||||||
success: function(res) {
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
fail: function() {
|
|
||||||
reject("选择文件失败")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 上传图片
|
|
||||||
export const uploadFile = (url,file) => {
|
|
||||||
let authorization = uni.getStorageSync('authorization');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.uploadFile({
|
|
||||||
url: baseUrl + url,
|
|
||||||
// filePath: file.tempFilePaths[0],
|
|
||||||
name: 'file',
|
|
||||||
header:{
|
|
||||||
authorization: authorization
|
|
||||||
},
|
|
||||||
formData: {
|
|
||||||
from: file,
|
|
||||||
},
|
|
||||||
complete: (res) => {
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
resolve(res)
|
|
||||||
} else {
|
|
||||||
reject(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
//滚动到元素位置
|
|
||||||
export const smoothScroll = (element) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
document.querySelector(element).scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentTime() {
|
|
||||||
var gettime = this
|
|
||||||
const yy = new Date().getFullYear()
|
|
||||||
const mm = new Date().getMonth() + 1
|
|
||||||
const dd = new Date().getDate()
|
|
||||||
const hh = new Date().getHours()
|
|
||||||
const mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes()
|
|
||||||
const ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds()
|
|
||||||
gettime = yy + '-' + mm + '-' + dd + ' ' + hh + ':' + mf + ':' + ss
|
|
||||||
|
|
||||||
return gettime
|
|
||||||
}
|
|
||||||
export function getCurrentTime1() {
|
|
||||||
var gettime = this
|
|
||||||
const yy = new Date().getFullYear()
|
|
||||||
const mm = new Date().getMonth() + 1
|
|
||||||
const dd = new Date().getDate()
|
|
||||||
const hh = new Date().getHours()
|
|
||||||
const mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes()
|
|
||||||
const ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds()
|
|
||||||
gettime = yy + '-' + mm + '-' + dd
|
|
||||||
|
|
||||||
return gettime
|
|
||||||
}
|
|
||||||
|
|
||||||
// 时间戳转日期(年月日)
|
|
||||||
export const time_format = (time) => {
|
|
||||||
// 判断时间戳是否为13位数,如果不是则*1000,时间戳只有13位数(带毫秒)和10(不带毫秒)位数的
|
|
||||||
if (time.toString().length == 13) {
|
|
||||||
var tme = new Date(time);
|
|
||||||
} else {
|
|
||||||
var tme = new Date(time * 1000);
|
|
||||||
}
|
|
||||||
var Y = tme.getFullYear();
|
|
||||||
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
|
|
||||||
var D = tme.getDate();
|
|
||||||
var h = tme.getHours();
|
|
||||||
var m = tme.getMinutes();
|
|
||||||
var s = tme.getSeconds();
|
|
||||||
if(D<10){
|
|
||||||
D='0'+D;
|
|
||||||
}
|
|
||||||
var tem1 = Y + '/' + M + '/' + D
|
|
||||||
// + h + '时' + m + '分'
|
|
||||||
// + s +'秒'
|
|
||||||
return tem1;
|
|
||||||
}
|
|
||||||
export const time_format3 = (time,seq="-") =>{
|
|
||||||
// 判断时间戳是否为13位数,如果不是则*1000,时间戳只有13位数(带毫秒)和10(不带毫秒)位数的
|
|
||||||
if(time.toString().length == 13){
|
|
||||||
var tme = new Date(time);
|
|
||||||
}else{
|
|
||||||
var tme = new Date(time * 1000);
|
|
||||||
}
|
|
||||||
var Y = tme.getFullYear();
|
|
||||||
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
|
|
||||||
var D = tme.getDate();
|
|
||||||
var h = tme.getHours();
|
|
||||||
if(h<10){
|
|
||||||
h='0'+h;
|
|
||||||
}
|
|
||||||
var m = tme.getMinutes();
|
|
||||||
if(m<10){
|
|
||||||
m='0'+m;
|
|
||||||
}
|
|
||||||
var s = tme.getSeconds();
|
|
||||||
if(s<10){
|
|
||||||
s='0'+s;
|
|
||||||
}
|
|
||||||
var tem1 = Y + seq + M + seq + D +' '+h+':'+m
|
|
||||||
// + h + '时' + m + '分'
|
|
||||||
// + s +'秒'
|
|
||||||
return tem1;
|
|
||||||
}
|
|
||||||
// 时间戳转日期(时分)
|
|
||||||
export const time_format1 = (time) =>{
|
|
||||||
if(time.toString().length == 13){
|
|
||||||
var tme = new Date(time);
|
|
||||||
}else{
|
|
||||||
var tme = new Date(time * 1000);
|
|
||||||
}
|
|
||||||
var Y = tme.getFullYear();
|
|
||||||
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
|
|
||||||
var D = tme.getDate();
|
|
||||||
var h = tme.getHours();
|
|
||||||
var m = tme.getMinutes();
|
|
||||||
var s = tme.getSeconds();
|
|
||||||
var tem2 = + h + '时' + m + '分'
|
|
||||||
// + s +'秒'
|
|
||||||
return tem2;
|
|
||||||
}
|
|
||||||
// 时间戳转日期(时分 00:00格式)
|
|
||||||
export const toHHmmss= (data)=> {
|
|
||||||
var time;
|
|
||||||
var hours = parseInt((data % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
||||||
var minutes = parseInt((data % (1000 * 60 * 60)) / (1000 * 60));
|
|
||||||
// var seconds = (data % (1000 * 60)) / 1000;
|
|
||||||
time = (hours < 10 ? ('0' + hours) : hours) + ':' + (minutes < 10 ? ('0' + minutes) : minutes) ;
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
// 日期转时间戳(10)
|
|
||||||
export const date_stamp = (time) => new Date(time).getTime() / 1000
|
|
||||||
// 日期转时间戳(13)
|
|
||||||
export const date_stamp1 = (time) => new Date(time).getTime()
|
|
||||||
// 树节点
|
|
||||||
export function travelTree(tree, arr) {
|
|
||||||
for (let item of tree) {
|
|
||||||
arr.push(item.label);
|
|
||||||
if (item.children && item.children.length) travelTree(item.children, arr);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
export const uniStorage = {
|
|
||||||
getItem(key) {
|
|
||||||
return uni.getStorageSync(key) || null
|
|
||||||
},
|
|
||||||
setItem(key, value) {
|
|
||||||
return uni.setStorageSync(key, value)
|
|
||||||
},
|
|
||||||
removeItem(key) {
|
|
||||||
return uni.removeStorageSync(key)
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export const uploadFile = (url: string, options) => {
|
|
||||||
console.log('options: ', options)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 574 B |
Before Width: | Height: | Size: 780 B |
Before Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.3 KiB |