如果管理后台没有启用会员充值菜单,移动端也不显示充值套餐功能

This commit is contained in:
GeekMaster
2025-05-27 16:49:31 +08:00
parent a7063bf30a
commit 6b6fe1bebd
7 changed files with 353 additions and 304 deletions

View File

@@ -10,6 +10,7 @@
- 功能优化:修改重新回答功能,撤回千面的问答内容为可编辑内容,撤回的内容不会增加额外的上下文 - 功能优化:修改重新回答功能,撤回千面的问答内容为可编辑内容,撤回的内容不会增加额外的上下文
- 功能优化:优化聊天记录的存储结构,增加模型名称字段,支持存储更长的模型名称 - 功能优化:优化聊天记录的存储结构,增加模型名称字段,支持存储更长的模型名称
- Bug 修复:聊天应用绑定模型后无效,还是会轮询 API KEY导致一会成功一会请求失败。 - Bug 修复:聊天应用绑定模型后无效,还是会轮询 API KEY导致一会成功一会请求失败。
- 功能优化:如果管理后台没有启用会员充值菜单,移动端也不显示充值套餐功能
## v4.2.3 ## v4.2.3

View File

@@ -6,35 +6,41 @@
</div> </div>
<div class="chat-icon"> <div class="chat-icon">
<van-image :src="icon"/> <van-image :src="icon" />
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import {onMounted, ref} from "vue"; import Clipboard from 'clipboard'
import Clipboard from "clipboard"; import { showNotify } from 'vant'
import {showNotify} from "vant"; import { onMounted, ref } from 'vue'
// eslint-disable-next-line no-unused-vars,no-undef // eslint-disable-next-line no-unused-vars,no-undef
const props = defineProps({ const props = defineProps({
content: { content: {
type: String, type: Object,
default: '', default: {
text: '',
files: [],
},
}, },
icon: { icon: {
type: String, type: String,
default: '/images/user-icon.png', default: '/images/user-icon.png',
} },
}); })
const contentRef = ref(null) const contentRef = ref(null)
const content = computed(() => {
return props.content.text
})
onMounted(() => { onMounted(() => {
const clipboard = new Clipboard(contentRef.value); const clipboard = new Clipboard(contentRef.value)
clipboard.on('success', () => { clipboard.on('success', () => {
showNotify({type: 'success', message: '复制成功', duration: 1000}) showNotify({ type: 'success', message: '复制成功', duration: 1000 })
}) })
clipboard.on('error', () => { clipboard.on('error', () => {
showNotify({type: 'danger', message: '复制失败', duration: 2000}) showNotify({ type: 'danger', message: '复制失败', duration: 2000 })
}) })
}) })
</script> </script>

View File

@@ -28,8 +28,11 @@ import { showImagePreview } from 'vant'
import Thinking from '../Thinking.vue' import Thinking from '../Thinking.vue'
const props = defineProps({ const props = defineProps({
content: { content: {
type: String, type: Object,
default: '', default: {
text: '',
files: [],
},
}, },
orgContent: { orgContent: {
type: String, type: String,
@@ -41,6 +44,9 @@ const props = defineProps({
}, },
}) })
const content = computed(() => {
return props.content.text
})
const contentRef = ref(null) const contentRef = ref(null)
onMounted(() => { onMounted(() => {
const imgs = contentRef.value.querySelectorAll('img') const imgs = contentRef.value.querySelectorAll('img')

View File

@@ -86,16 +86,15 @@ import ThemeChange from '@/components/ThemeChange.vue'
import { checkSession, getLicenseInfo, getSystemInfo } from '@/store/cache' import { checkSession, getLicenseInfo, getSystemInfo } from '@/store/cache'
import { removeUserToken } from '@/store/session' import { removeUserToken } from '@/store/session'
import { httpGet } from '@/utils/http' import { httpGet } from '@/utils/http'
import { isMobile } from '@/utils/libs'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { onMounted, onUnmounted, ref } from 'vue' import { onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
if (isMobile()) { // if (isMobile()) {
router.push('/mobile/index') // router.push('/mobile/index')
} // }
const title = ref('') const title = ref('')
const logo = ref('') const logo = ref('')

View File

@@ -15,43 +15,47 @@
<div class="chat-list-wrapper"> <div class="chat-list-wrapper">
<div id="message-list-box" class="message-list-box"> <div id="message-list-box" class="message-list-box">
<van-list <van-list
v-model:error="error" v-model:error="error"
:finished="finished" :finished="finished"
error-text="请求失败点击重新加载" error-text="请求失败点击重新加载"
@load="onLoad" @load="onLoad"
> >
<van-cell v-for="item in chatData" :key="item" :border="false" class="message-line"> <van-cell v-for="item in chatData" :key="item" :border="false" class="message-line">
<chat-prompt <chat-prompt
v-if="item.type==='prompt'" v-if="item.type === 'prompt'"
:content="item.content" :content="item.content"
:created-at="dateFormat(item['created_at'])" :created-at="dateFormat(item['created_at'])"
:icon="item.icon" :icon="item.icon"
:tokens="item['tokens']"/> :tokens="item['tokens']"
<chat-reply v-else-if="item.type==='reply'" />
:content="item.content" <chat-reply
:created-at="dateFormat(item['created_at'])" v-else-if="item.type === 'reply'"
:icon="item.icon" :content="item.content"
:org-content="item.orgContent" :created-at="dateFormat(item['created_at'])"
:tokens="item['tokens']"/> :icon="item.icon"
:org-content="item.orgContent"
:tokens="item['tokens']"
/>
</van-cell> </van-cell>
</van-list> </van-list>
</div> </div>
</div> </div>
</div>
</div><!-- end chat box --> <!-- end chat box -->
</div> </div>
</template> </template>
<script setup> <script setup>
import ChatPrompt from '@/components/mobile/ChatPrompt.vue'
import {dateFormat, processContent} from "@/utils/libs"; import ChatReply from '@/components/mobile/ChatReply.vue'
import ChatReply from "@/components/mobile/ChatReply.vue"; import { httpGet } from '@/utils/http'
import ChatPrompt from "@/components/mobile/ChatPrompt.vue"; import { dateFormat, processContent } from '@/utils/libs'
import {nextTick, ref} from "vue"; import hl from 'highlight.js'
import {useRouter} from "vue-router";
import {httpGet} from "@/utils/http";
import 'highlight.js/styles/a11y-dark.css' import 'highlight.js/styles/a11y-dark.css'
import hl from "highlight.js"; import MarkdownIt from 'markdown-it'
import {showFailToast} from "vant"; import mathjaxPlugin from 'markdown-it-mathjax3'
import { showFailToast } from 'vant'
import { nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
const chatData = ref([]) const chatData = ref([])
const router = useRouter() const router = useRouter()
@@ -62,8 +66,7 @@ const model = ref('')
const finished = ref(false) const finished = ref(false)
const error = ref(false) const error = ref(false)
const mathjaxPlugin = require('markdown-it-mathjax3') const md = new MarkdownIt({
const md = require('markdown-it')({
breaks: true, breaks: true,
html: true, html: true,
linkify: true, linkify: true,
@@ -72,11 +75,14 @@ const md = require('markdown-it')({
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000) const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
// 显示复制代码按钮 // 显示复制代码按钮
const copyBtn = `<span class="copy-code-mobile" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span> const copyBtn = `<span class="copy-code-mobile" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '&lt;/textarea>')}</textarea>` <textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
/<\/textarea>/g,
'&lt;/textarea>'
)}</textarea>`
if (lang && hl.getLanguage(lang)) { if (lang && hl.getLanguage(lang)) {
const langHtml = `<span class="lang-name">${lang}</span>` const langHtml = `<span class="lang-name">${lang}</span>`
// 处理代码高亮 // 处理代码高亮
const preCode = hl.highlight(lang, str, true).value const preCode = hl.highlight(str, { language: lang }).value
// 将代码包裹在 pre 中 // 将代码包裹在 pre 中
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>` return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
} }
@@ -84,50 +90,52 @@ const md = require('markdown-it')({
// 处理代码高亮 // 处理代码高亮
const preCode = md.utils.escapeHtml(str) const preCode = md.utils.escapeHtml(str)
// 将代码包裹在 pre 中 // 将代码包裹在 pre 中
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>` return `<pre class="code-container">${code}${copyBtn}</pre>`
} },
}); })
md.use(mathjaxPlugin) md.use(mathjaxPlugin)
const onLoad = () => { const onLoad = () => {
httpGet('/api/chat/history?chat_id=' + chatId).then(res => { httpGet('/api/chat/history?chat_id=' + chatId)
// 加载状态结束 .then((res) => {
finished.value = true; // 加载状态结束
const data = res.data finished.value = true
if (data && data.length > 0) { const data = res.data
for (let i = 0; i < data.length; i++) { if (data && data.length > 0) {
if (data[i].type === "prompt") { for (let i = 0; i < data.length; i++) {
chatData.value.push(data[i]); if (data[i].type === 'prompt') {
continue; chatData.value.push(data[i])
continue
}
data[i].orgContent = data[i].content
data[i].content = md.render(processContent(data[i].content))
chatData.value.push(data[i])
} }
data[i].orgContent = data[i].content; nextTick(() => {
data[i].content = md.render(processContent(data[i].content)) hl.configure({ ignoreUnescapedHTML: true })
chatData.value.push(data[i]); const blocks = document.querySelector('#message-list-box').querySelectorAll('pre code')
} blocks.forEach((block) => {
hl.highlightElement(block)
nextTick(() => { })
hl.configure({ignoreUnescapedHTML: true})
const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
}) })
}) }
} })
}).catch(() => { .catch(() => {
error.value = true error.value = true
}) })
httpGet(`/api/chat/detail?chat_id=${chatId}`).then(res => {
title.value = res.data.title
model.value = res.data.model
role.value = res.data.role_name
}).catch(e => {
showFailToast('加载对话失败:' + e.message)
})
};
httpGet(`/api/chat/detail?chat_id=${chatId}`)
.then((res) => {
title.value = res.data.title
model.value = res.data.model
role.value = res.data.role_name
})
.catch((e) => {
showFailToast('加载对话失败:' + e.message)
})
}
</script> </script>
<style scoped lang="stylus"> <style scoped lang="stylus">
.chat-export-mobile { .chat-export-mobile {

View File

@@ -120,11 +120,13 @@
<script setup> <script setup>
import ChatPrompt from '@/components/mobile/ChatPrompt.vue' import ChatPrompt from '@/components/mobile/ChatPrompt.vue'
import ChatReply from '@/components/mobile/ChatReply.vue' import ChatReply from '@/components/mobile/ChatReply.vue'
import { checkSession, getClientId } from '@/store/cache' import { checkSession } from '@/store/cache'
import { getUserToken } from '@/store/session'
import { useSharedStore } from '@/store/sharedata' import { useSharedStore } from '@/store/sharedata'
import { showMessageError } from '@/utils/dialog' import { showMessageError } from '@/utils/dialog'
import { httpGet } from '@/utils/http' import { httpGet } from '@/utils/http'
import { processContent, randString, renderInputText, UUID } from '@/utils/libs' import { processContent, randString, renderInputText, UUID } from '@/utils/libs'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import Clipboard from 'clipboard' import Clipboard from 'clipboard'
import hl from 'highlight.js' import hl from 'highlight.js'
import 'highlight.js/styles/a11y-dark.css' import 'highlight.js/styles/a11y-dark.css'
@@ -134,6 +136,7 @@ import mathjaxPlugin from 'markdown-it-mathjax3'
import { showImagePreview, showNotify, showToast } from 'vant' import { showImagePreview, showNotify, showToast } from 'vant'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue' import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const winHeight = ref(0) const winHeight = ref(0)
const navBarRef = ref(null) const navBarRef = ref(null)
const bottomBarRef = ref(null) const bottomBarRef = ref(null)
@@ -267,65 +270,10 @@ onMounted(() => {
clipboard.on('error', () => { clipboard.on('error', () => {
showNotify({ type: 'danger', message: '复制失败', duration: 2000 }) showNotify({ type: 'danger', message: '复制失败', duration: 2000 })
}) })
store.addMessageHandler('chat', (data) => {
if (data.channel !== 'chat' || data.clientId !== getClientId()) {
return
}
if (data.type === 'error') {
showMessageError(data.body)
return
}
if (isNewMsg.value) {
if (!title.value) {
title.value = previousText.value
}
lineBuffer.value = data.body
isNewMsg.value = false
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
reply['content'] = lineBuffer.value
}
} else if (data.type === 'end') {
// 消息接收完毕
enableInput()
lineBuffer.value = '' // 清空缓冲
isNewMsg.value = true
} else {
lineBuffer.value += data.body
const reply = chatData.value[chatData.value.length - 1]
reply['orgContent'] = lineBuffer.value
reply['content'] = md.render(processContent(lineBuffer.value))
nextTick(() => {
hl.configure({ ignoreUnescapedHTML: true })
const lines = document.querySelectorAll('.message-line')
const blocks = lines[lines.length - 1].querySelectorAll('pre code')
blocks.forEach((block) => {
hl.highlightElement(block)
})
scrollListBox()
const items = document.querySelectorAll('.message-line')
const imgs = items[items.length - 1].querySelectorAll('img')
for (let i = 0; i < imgs.length; i++) {
if (!imgs[i].src) {
continue
}
imgs[i].addEventListener('click', (e) => {
e.stopPropagation()
showImagePreview([imgs[i].src])
})
}
})
}
})
}) })
onUnmounted(() => { onUnmounted(() => {
store.removeMessageHandler('chat') // Remove WebSocket handler cleanup
}) })
const newChat = (item) => { const newChat = (item) => {
@@ -360,7 +308,9 @@ const loadChatHistory = () => {
type: 'reply', type: 'reply',
id: randString(32), id: randString(32),
icon: role.icon, icon: role.icon,
content: role.hello_msg, content: {
text: role.hello_msg,
},
orgContent: role.hello_msg, orgContent: role.hello_msg,
}) })
return return
@@ -373,7 +323,7 @@ const loadChatHistory = () => {
} }
data[i].orgContent = data[i].content data[i].orgContent = data[i].content
data[i].content = md.render(processContent(data[i].content)) data[i].content.text = md.render(processContent(data[i].content.text))
chatData.value.push(data[i]) chatData.value.push(data[i])
} }
@@ -427,17 +377,106 @@ const scrollListBox = () => {
.scrollTo(0, document.getElementById('message-list-box').scrollHeight + 46) .scrollTo(0, document.getElementById('message-list-box').scrollHeight + 46)
} }
// 发送 SSE 请求
const sendSSERequest = async (message) => {
try {
await fetchEventSource('/api/chat/message', {
method: 'POST',
headers: {
Authorization: getUserToken(),
},
body: JSON.stringify(message),
openWhenHidden: true,
onopen(response) {
if (response.ok && response.status === 200) {
console.log('SSE connection opened')
} else {
throw new Error(`Failed to open SSE connection: ${response.status}`)
}
},
onmessage(msg) {
try {
const data = JSON.parse(msg.data)
if (data.type === 'error') {
showMessageError(data.body)
enableInput()
return
}
if (data.type === 'end') {
enableInput()
lineBuffer.value = '' // 清空缓冲
isNewMsg.value = true
return
}
if (data.type === 'text') {
if (isNewMsg.value) {
isNewMsg.value = false
lineBuffer.value = data.body
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
reply['content']['text'] = lineBuffer.value
}
} else {
lineBuffer.value += data.body
const reply = chatData.value[chatData.value.length - 1]
reply['orgContent'] = lineBuffer.value
reply['content']['text'] = md.render(processContent(lineBuffer.value))
nextTick(() => {
hl.configure({ ignoreUnescapedHTML: true })
const lines = document.querySelectorAll('.message-line')
const blocks = lines[lines.length - 1].querySelectorAll('pre code')
blocks.forEach((block) => {
hl.highlightElement(block)
})
scrollListBox()
const items = document.querySelectorAll('.message-line')
const imgs = items[items.length - 1].querySelectorAll('img')
for (let i = 0; i < imgs.length; i++) {
if (!imgs[i].src) {
continue
}
imgs[i].addEventListener('click', (e) => {
e.stopPropagation()
showImagePreview([imgs[i].src])
})
}
})
}
}
} catch (error) {
console.error('Error processing message:', error)
enableInput()
showMessageError('消息处理出错,请重试')
}
},
onerror(err) {
console.error('SSE Error:', err)
enableInput()
showMessageError('连接已断开,请重试')
},
onclose() {
console.log('SSE connection closed')
enableInput()
},
})
} catch (error) {
console.error('Failed to send message:', error)
enableInput()
showMessageError('发送消息失败,请重试')
}
}
// 发送消息
const sendMessage = () => { const sendMessage = () => {
if (canSend.value === false) { if (canSend.value === false) {
showToast('AI 正在作答中,请稍后...') showToast('AI 正在作答中,请稍后...')
return return
} }
if (store.socket.conn.readyState !== WebSocket.OPEN) {
showToast('连接断开,正在重连...')
return
}
if (prompt.value.trim().length === 0) { if (prompt.value.trim().length === 0) {
showToast('请输入需要 AI 回答的问题') showToast('请输入需要 AI 回答的问题')
return false return false
@@ -448,7 +487,7 @@ const sendMessage = () => {
type: 'prompt', type: 'prompt',
id: randString(32), id: randString(32),
icon: loginUser.value.avatar, icon: loginUser.value.avatar,
content: renderInputText(prompt.value), content: { text: renderInputText(prompt.value) },
created_at: new Date().getTime(), created_at: new Date().getTime(),
}) })
// 添加空回复消息 // 添加空回复消息
@@ -459,7 +498,9 @@ const sendMessage = () => {
type: 'reply', type: 'reply',
id: randString(32), id: randString(32),
icon: _role['icon'], icon: _role['icon'],
content: '', content: {
text: '',
},
}) })
nextTick(() => { nextTick(() => {
@@ -467,31 +508,23 @@ const sendMessage = () => {
}) })
disableInput(false) disableInput(false)
store.socket.conn.send(
JSON.stringify({ // 发送 SSE 请求
channel: 'chat', sendSSERequest({
type: 'text', user_id: loginUser.value.id,
body: { role_id: roleId.value,
role_id: roleId.value, model_id: modelId.value,
model_id: modelId.value, chat_id: chatId.value,
chat_id: chatId.value, prompt: prompt.value,
content: prompt.value, stream: stream.value,
stream: stream.value, })
},
})
)
previousText.value = prompt.value previousText.value = prompt.value
prompt.value = '' prompt.value = ''
return true return true
} }
const stopGenerate = () => { // 重新生成
showStopGenerate.value = false
httpGet('/api/chat/stop?session_id=' + getClientId()).then(() => {
enableInput()
})
}
const reGenerate = () => { const reGenerate = () => {
disableInput(false) disableInput(false)
const text = '重新生成上述问题的答案:' + previousText.value const text = '重新生成上述问题的答案:' + previousText.value
@@ -502,19 +535,16 @@ const reGenerate = () => {
icon: loginUser.value.avatar, icon: loginUser.value.avatar,
content: renderInputText(text), content: renderInputText(text),
}) })
store.socket.conn.send(
JSON.stringify({ // 发送 SSE 请求
channel: 'chat', sendSSERequest({
type: 'text', user_id: loginUser.value.id,
body: { role_id: roleId.value,
role_id: roleId.value, model_id: modelId.value,
model_id: modelId.value, chat_id: chatId.value,
chat_id: chatId.value, prompt: previousText.value,
content: previousText.value, stream: stream.value,
stream: stream.value, })
},
})
)
} }
const showShare = ref(false) const showShare = ref(false)

View File

@@ -30,28 +30,36 @@
<div class="opt" v-if="isLogin"> <div class="opt" v-if="isLogin">
<van-row :gutter="10"> <van-row :gutter="10">
<van-col :span="8"> <van-col :span="8">
<van-button round block @click="showPasswordDialog = true" size="small">修改密码</van-button> <van-button round block @click="showPasswordDialog = true" size="small"
>修改密码</van-button
>
</van-col> </van-col>
<van-col :span="8"> <van-col :span="8">
<van-button round block @click="logout" size="small">退出登录</van-button> <van-button round block @click="logout" size="small">退出登录</van-button>
</van-col> </van-col>
<van-col :span="8"> <van-col :span="8">
<van-button round block @click="showSettings = true" icon="setting" size="small">设置</van-button> <van-button round block @click="showSettings = true" icon="setting" size="small"
>设置</van-button
>
</van-col> </van-col>
</van-row> </van-row>
</div> </div>
<div class="product-list"> <div class="product-list" v-if="menuList['/member']">
<h3>充值套餐</h3> <h3 class="py-3">充值套餐</h3>
<div class="item" v-for="item in products" :key="item.id"> <div class="item" v-for="item in products" :key="item.id">
<div class="title"> <div class="title">
<span class="name">{{ item.name }}</span> <span class="name">{{ item.name }}</span>
<div class="pay-btn"> <div class="pay-btn">
<div v-for="payWay in payWays" @click="pay(item, payWay)" :key="payWay"> <div v-for="payWay in payWays" @click="pay(item, payWay)" :key="payWay">
<span> <span>
<van-button type="primary" size="small" v-if="payWay.pay_type === 'alipay'"> <i class="iconfont icon-alipay"></i> 支付宝 </van-button> <van-button type="primary" size="small" v-if="payWay.pay_type === 'alipay'">
<van-button type="success" size="small" v-if="payWay.pay_type === 'wxpay'"> <i class="iconfont icon-wechat-pay"></i> 微信支付 </van-button> <i class="iconfont icon-alipay"></i> 支付宝
</van-button>
<van-button type="success" size="small" v-if="payWay.pay_type === 'wxpay'">
<i class="iconfont icon-wechat-pay"></i> 微信支付
</van-button>
</span> </span>
</div> </div>
</div> </div>
@@ -100,7 +108,10 @@
<van-cell-group inset> <van-cell-group inset>
<van-field name="switch" label="暗黑主题"> <van-field name="switch" label="暗黑主题">
<template #input> <template #input>
<van-switch v-model="dark" @change="(val) => store.setTheme(val ? 'dark' : 'light')" /> <van-switch
v-model="dark"
@change="(val) => store.setTheme(val ? 'dark' : 'light')"
/>
</template> </template>
</van-field> </van-field>
@@ -130,163 +141,151 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { checkSession, getSystemInfo } from '@/store/cache'
import { showFailToast, showLoadingToast, showNotify, showSuccessToast } from "vant"; import { removeUserToken } from '@/store/session'
import { httpGet, httpPost } from "@/utils/http"; import { useSharedStore } from '@/store/sharedata'
import { dateFormat, showLoginDialog } from "@/utils/libs"; import { httpGet, httpPost } from '@/utils/http'
import { ElMessage } from "element-plus"; import { dateFormat, showLoginDialog } from '@/utils/libs'
import { checkSession, getSystemInfo } from "@/store/cache"; import { ElMessage } from 'element-plus'
import { useRouter } from "vue-router"; import { showFailToast, showLoadingToast, showNotify, showSuccessToast } from 'vant'
import { removeUserToken } from "@/store/session"; import { onMounted, ref } from 'vue'
import { useSharedStore } from "@/store/sharedata"; import { useRouter } from 'vue-router'
const form = ref({ const form = ref({
username: "GeekMaster", username: 'GeekMaster',
nickname: "极客学长@001", nickname: '极客学长@001',
mobile: "1300000000", mobile: '1300000000',
avatar: "", avatar: '',
power: 0, power: 0,
}); })
const fileList = ref([ const fileList = ref([
{ {
url: "/images/user-info.png", url: '/images/user-info.png',
message: "上传中...", message: '上传中...',
}, },
]); ])
const products = ref([]); const products = ref([])
const vipMonthPower = ref(0); const vipMonthPower = ref(0)
const payWays = ref({}); const payWays = ref({})
const router = useRouter(); const router = useRouter()
const userId = ref(0); const userId = ref(0)
const isLogin = ref(false); const isLogin = ref(false)
const showSettings = ref(false); const showSettings = ref(false)
const store = useSharedStore(); const store = useSharedStore()
const stream = ref(store.chatStream); const stream = ref(store.chatStream)
const dark = ref(store.theme === "dark"); const dark = ref(store.theme === 'dark')
const menuList = ref({})
onMounted(() => { onMounted(() => {
checkSession() checkSession()
.then((user) => { .then((user) => {
userId.value = user.id; userId.value = user.id
isLogin.value = true; isLogin.value = true
httpGet("/api/user/profile") httpGet('/api/user/profile')
.then((res) => { .then((res) => {
form.value = res.data; form.value = res.data
fileList.value[0].url = form.value.avatar; fileList.value[0].url = form.value.avatar
}) })
.catch((e) => { .catch((e) => {
console.log(e.message); console.log(e.message)
showFailToast("获取用户信息失败"); showFailToast('获取用户信息失败')
}); })
}) })
.catch(() => {}); .catch(() => {})
// 获取产品列表 // 获取产品列表
httpGet("/api/product/list") httpGet('/api/product/list')
.then((res) => { .then((res) => {
products.value = res.data; products.value = res.data
}) })
.catch((e) => { .catch((e) => {
showFailToast("获取产品套餐失败:" + e.message); showFailToast('获取产品套餐失败:' + e.message)
}); })
getSystemInfo() getSystemInfo()
.then((res) => { .then((res) => {
vipMonthPower.value = res.data["vip_month_power"]; vipMonthPower.value = res.data['vip_month_power']
}) })
.catch((e) => { .catch((e) => {
showFailToast("获取系统配置失败:" + e.message); showFailToast('获取系统配置失败:' + e.message)
}); })
httpGet("/api/payment/payWays") httpGet('/api/payment/payWays')
.then((res) => { .then((res) => {
payWays.value = res.data; payWays.value = res.data
}) })
.catch((e) => { .catch((e) => {
ElMessage.error("获取支付方式失败:" + e.message); ElMessage.error('获取支付方式失败:' + e.message)
}); })
});
// const afterRead = (file) => { getMenuList()
// file.status = 'uploading'; })
// file.message = '上传中...';
// // 压缩图片并上传
// new Compressor(file.file, {
// quality: 0.6,
// success(result) {
// const formData = new FormData();
// formData.append('file', result, result.name);
// // 执行上传操作
// httpPost('/api/upload', formData).then((res) => {
// form.value.avatar = res.data.url
// file.status = 'success'
// httpPost('/api/user/profile/update', form.value).then(() => {
// showSuccessToast('上传成功')
// }).catch(() => {
// showFailToast('上传失败')
// })
// }).catch((e) => {
// showNotify({type: 'danger', message: '上传失败:' + e.message})
// })
// },
// error(err) {
// console.log(err.message);
// },
// });
// }
const showPasswordDialog = ref(false); // 获取菜单列表
const getMenuList = () => {
httpGet('/api/menu/list')
.then((res) => {
res.data.forEach((item) => {
menuList.value[item.url] = item
})
})
.catch((e) => {
showFailToast('获取菜单列表失败:' + e.message)
})
}
const showPasswordDialog = ref(false)
const pass = ref({ const pass = ref({
old: "", old: '',
new: "", new: '',
renew: "", renew: '',
}); })
const beforeClose = (action) => { const beforeClose = (action) => {
new Promise((resolve) => { new Promise((resolve) => {
resolve(action === "confirm"); resolve(action === 'confirm')
}); })
}; }
// 提交修改密码 // 提交修改密码
const updatePass = () => { const updatePass = () => {
if (pass.value.old === "") { if (pass.value.old === '') {
return showNotify({ type: "danger", message: "请输入旧密码" }); return showNotify({ type: 'danger', message: '请输入旧密码' })
} }
if (!pass.value.new || pass.value.new.length < 8) { if (!pass.value.new || pass.value.new.length < 8) {
return showNotify({ type: "danger", message: "密码的长度为8-16个字符" }); return showNotify({ type: 'danger', message: '密码的长度为8-16个字符' })
} }
if (pass.value.renew !== pass.value.new) { if (pass.value.renew !== pass.value.new) {
return showNotify({ type: "danger", message: "两次输入密码不一致" }); return showNotify({ type: 'danger', message: '两次输入密码不一致' })
} }
httpPost("/api/user/password", { httpPost('/api/user/password', {
old_pass: pass.value.old, old_pass: pass.value.old,
password: pass.value.new, password: pass.value.new,
repass: pass.value.renew, repass: pass.value.renew,
}) })
.then(() => { .then(() => {
showSuccessToast("更新成功!"); showSuccessToast('更新成功!')
showPasswordDialog.value = false; showPasswordDialog.value = false
}) })
.catch((e) => { .catch((e) => {
showFailToast("更新失败," + e.message); showFailToast('更新失败,' + e.message)
showPasswordDialog.value = false; showPasswordDialog.value = false
}); })
}; }
const pay = (product, payWay) => { const pay = (product, payWay) => {
if (!isLogin.value) { if (!isLogin.value) {
return showLoginDialog(router); return showLoginDialog(router)
} }
showLoadingToast({ showLoadingToast({
message: "正在创建订单", message: '正在创建订单',
forbidClick: true, forbidClick: true,
}); })
let host = process.env.VUE_APP_API_HOST; let host = process.env.VUE_APP_API_HOST
if (host === "") { if (host === '') {
host = `${location.protocol}//${location.host}`; host = `${location.protocol}//${location.host}`
} }
httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, { httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
product_id: product.id, product_id: product.id,
@@ -294,27 +293,27 @@ const pay = (product, payWay) => {
pay_type: payWay.pay_type, pay_type: payWay.pay_type,
user_id: userId.value, user_id: userId.value,
host: host, host: host,
device: "wechat", device: 'wechat',
}) })
.then((res) => { .then((res) => {
location.href = res.data; location.href = res.data
}) })
.catch((e) => { .catch((e) => {
showFailToast("生成支付订单失败:" + e.message); showFailToast('生成支付订单失败:' + e.message)
}); })
}; }
const logout = function () { const logout = function () {
httpGet("/api/user/logout") httpGet('/api/user/logout')
.then(() => { .then(() => {
removeUserToken(); removeUserToken()
store.setIsLogin(false); store.setIsLogin(false)
router.push("/"); router.push('/')
}) })
.catch(() => { .catch(() => {
showFailToast("注销失败!"); showFailToast('注销失败!')
}); })
}; }
</script> </script>
<style lang="stylus"> <style lang="stylus">