mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-13 04:33:42 +08:00
style:样式切换
This commit is contained in:
@@ -256,7 +256,7 @@ const reGenerate = (prompt) => {
|
||||
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color #e7e7e8
|
||||
background-color var(--el-color-primary-light-3)
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
@@ -348,7 +348,7 @@ const reGenerate = (prompt) => {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
background-color #e7e7e8;
|
||||
background-color var( --little-btn-bg);
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
@@ -433,7 +433,7 @@ const reGenerate = (prompt) => {
|
||||
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color #e7e7e8
|
||||
background-color var( --little-btn-bg)
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
@@ -539,7 +539,7 @@ const reGenerate = (prompt) => {
|
||||
}
|
||||
|
||||
.bar-item.bg {
|
||||
background-color #e7e7e8
|
||||
background-color var( --gray-btn-bg)
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
|
||||
@@ -1,76 +1,90 @@
|
||||
<template>
|
||||
<div class="invite-list" v-loading="loading">
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
--el-table-tr-bg-color:#2D323B;
|
||||
--el-table-row-hover-bg-color:#373C47;
|
||||
--el-table-header-bg-color:#474E5C;
|
||||
--el-table-text-color:#d1d1d1">
|
||||
<el-table-column prop="username" label="用户"/>
|
||||
<el-table-column prop="invite_code" label="邀请码"/>
|
||||
<el-table-column prop="remark" label="邀请奖励"/>
|
||||
<el-table
|
||||
:data="items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
border
|
||||
style="
|
||||
--el-table-border-color: #373c47;
|
||||
--el-table-tr-bg-color: #2d323b;
|
||||
--el-table-row-hover-bg-color: #373c47;
|
||||
--el-table-header-bg-color: #474e5c;
|
||||
--el-table-text-color: #d1d1d1;
|
||||
"
|
||||
>
|
||||
<el-table-column prop="username" label="用户" />
|
||||
<el-table-column prop="invite_code" label="邀请码" />
|
||||
<el-table-column prop="remark" label="邀请奖励" />
|
||||
|
||||
<el-table-column label="注册时间">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" :image="nodata" description="暂无数据" v-else />
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="total > 0" background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
@current-change="fetchData()"
|
||||
:total="total"/>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||
@current-change="fetchData()"
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const loading = ref(true)
|
||||
const items = ref([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
const clipboard = new Clipboard('.copy-order-no');
|
||||
clipboard.on('success', () => {
|
||||
fetchData();
|
||||
const clipboard = new Clipboard(".copy-order-no");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
httpGet('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.page_size
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
})
|
||||
}
|
||||
httpGet("/api/invite/list", { page: page.value, page_size: pageSize.value })
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items;
|
||||
total.value = res.data.total;
|
||||
page.value = res.data.page;
|
||||
pageSize.value = res.data.page_size;
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
@@ -90,4 +104,4 @@ const fetchData = () => {
|
||||
color #20a0ff
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-container class="realtime-conversation" :style="{height: height}">
|
||||
<el-container class="realtime-conversation" :style="{ height: height }">
|
||||
<!-- connection animation -->
|
||||
<el-container class="connection-container" v-if="!isConnected">
|
||||
<div class="phone-container">
|
||||
@@ -36,14 +36,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="call-controls">
|
||||
<el-tooltip content="长按发送语音" placement="top" effect="light">
|
||||
<el-tooltip content="长按发送语音" placement="top">
|
||||
<ripple-button>
|
||||
<button class="call-button answer" @mousedown="startRecording" @mouseup="stopRecording">
|
||||
<button
|
||||
class="call-button answer"
|
||||
@mousedown="startRecording"
|
||||
@mouseup="stopRecording"
|
||||
>
|
||||
<i class="iconfont icon-mic-bold"></i>
|
||||
</button>
|
||||
</ripple-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="结束通话" placement="top" effect="light">
|
||||
<el-tooltip content="结束通话" placement="top">
|
||||
<button class="call-button hangup" @click="hangUp">
|
||||
<i class="iconfont icon-hung-up"></i>
|
||||
</button>
|
||||
@@ -51,32 +55,31 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-container>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import RippleButton from "@/components/ui/RippleButton.vue";
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { RealtimeClient } from '@openai/realtime-api-beta';
|
||||
import { WavRecorder, WavStreamPlayer } from '@/lib/wavtools/index.js';
|
||||
import { instructions } from '@/utils/conversation_config.js';
|
||||
import { WavRenderer } from '@/utils/wav_renderer';
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import {getUserToken} from "@/store/session";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { RealtimeClient } from "@openai/realtime-api-beta";
|
||||
import { WavRecorder, WavStreamPlayer } from "@/lib/wavtools/index.js";
|
||||
import { instructions } from "@/utils/conversation_config.js";
|
||||
import { WavRenderer } from "@/utils/wav_renderer";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
import { getUserToken } from "@/store/session";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars,no-undef
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: String,
|
||||
default: '100vh'
|
||||
default: "100vh"
|
||||
}
|
||||
})
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['close']);
|
||||
const emits = defineEmits(["close"]);
|
||||
|
||||
/********************** connection animation code *************************/
|
||||
const fullText = "正在接通中...";
|
||||
const connectingText = ref("")
|
||||
const connectingText = ref("");
|
||||
let index = 0;
|
||||
const typeText = () => {
|
||||
if (index < fullText.length) {
|
||||
@@ -85,12 +88,12 @@ const typeText = () => {
|
||||
setTimeout(typeText, 200); // 每300毫秒显示一个字
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
connectingText.value = '';
|
||||
connectingText.value = "";
|
||||
index = 0;
|
||||
typeText();
|
||||
}, 1000); // 等待1秒后重新开始
|
||||
}
|
||||
}
|
||||
};
|
||||
/*************************** end of code ****************************************/
|
||||
|
||||
/********************** conversation process code ***************************/
|
||||
@@ -102,31 +105,29 @@ const animateVoice = () => {
|
||||
rightVoiceActive.value = Math.random() > 0.5;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const wavRecorder = ref(new WavRecorder({ sampleRate: 24000 }));
|
||||
const wavStreamPlayer = ref(new WavStreamPlayer({ sampleRate: 24000 }));
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
host = 'wss://' + location.host;
|
||||
let host = process.env.VUE_APP_WS_HOST;
|
||||
if (host === "") {
|
||||
if (location.protocol === "https:") {
|
||||
host = "wss://" + location.host;
|
||||
} else {
|
||||
host = 'ws://' + location.host;
|
||||
host = "ws://" + location.host;
|
||||
}
|
||||
}
|
||||
const client = ref(
|
||||
new RealtimeClient({
|
||||
url: `${host}/api/realtime`,
|
||||
apiKey: getUserToken(),
|
||||
dangerouslyAllowAPIKeyInBrowser: true,
|
||||
})
|
||||
new RealtimeClient({
|
||||
url: `${host}/api/realtime`,
|
||||
apiKey: getUserToken(),
|
||||
dangerouslyAllowAPIKeyInBrowser: true
|
||||
})
|
||||
);
|
||||
// // Set up client instructions and transcription
|
||||
client.value.updateSession({
|
||||
instructions: instructions,
|
||||
turn_detection: null,
|
||||
input_audio_transcription: { model: 'whisper-1' },
|
||||
voice: 'alloy',
|
||||
input_audio_transcription: { model: "whisper-1" },
|
||||
voice: "alloy"
|
||||
});
|
||||
|
||||
// set voice wave canvas
|
||||
@@ -137,62 +138,66 @@ const isRecording = ref(false);
|
||||
const backgroundAudio = ref(null);
|
||||
const hangUpAudio = ref(null);
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
const connect = async () => {
|
||||
if (isConnected.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 播放背景音乐
|
||||
if (backgroundAudio.value) {
|
||||
backgroundAudio.value.play().catch(error => {
|
||||
console.error('播放失败,可能是浏览器的自动播放策略导致的:', error);
|
||||
backgroundAudio.value.play().catch((error) => {
|
||||
console.error("播放失败,可能是浏览器的自动播放策略导致的:", error);
|
||||
});
|
||||
}
|
||||
// 模拟拨号延时
|
||||
await sleep(3000)
|
||||
await sleep(3000);
|
||||
try {
|
||||
await client.value.connect();
|
||||
await wavRecorder.value.begin();
|
||||
await wavStreamPlayer.value.connect();
|
||||
console.log("对话连接成功!")
|
||||
console.log("对话连接成功!");
|
||||
if (!client.value.isConnected()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
isConnected.value = true;
|
||||
backgroundAudio.value?.pause()
|
||||
backgroundAudio.value.currentTime = 0
|
||||
backgroundAudio.value?.pause();
|
||||
backgroundAudio.value.currentTime = 0;
|
||||
client.value.sendUserMessageContent([
|
||||
{
|
||||
type: 'input_text',
|
||||
text: '你好,我是极客学长!',
|
||||
},
|
||||
type: "input_text",
|
||||
text: "你好,我是极客学长!"
|
||||
}
|
||||
]);
|
||||
if (client.value.getTurnDetectionType() === 'server_vad') {
|
||||
await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
|
||||
if (client.value.getTurnDetectionType() === "server_vad") {
|
||||
await wavRecorder.value.record((data) =>
|
||||
client.value.appendInputAudio(data.mono)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始语音输入
|
||||
const startRecording = async () => {
|
||||
if (isRecording.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
isRecording.value = true;
|
||||
try {
|
||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||
if (trackSampleOffset?.trackId) {
|
||||
const { trackId, offset } = trackSampleOffset;
|
||||
client.value.cancelResponse(trackId, offset);
|
||||
}
|
||||
await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
|
||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||
if (trackSampleOffset?.trackId) {
|
||||
const { trackId, offset } = trackSampleOffset;
|
||||
client.value.cancelResponse(trackId, offset);
|
||||
}
|
||||
await wavRecorder.value.record((data) =>
|
||||
client.value.appendInputAudio(data.mono)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,7 +208,7 @@ const stopRecording = async () => {
|
||||
await wavRecorder.value.pause();
|
||||
client.value.createResponse();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,13 +237,13 @@ const initialize = async () => {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
}
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const result = wavRecorder.value.recording
|
||||
? wavRecorder.value.getFrequencies('voice')
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, '#0099ff', 10, 0, 8);
|
||||
? wavRecorder.value.getFrequencies("voice")
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, "#0099ff", 10, 0, 8);
|
||||
}
|
||||
}
|
||||
if (serverCanvasRef.value) {
|
||||
@@ -247,13 +252,13 @@ const initialize = async () => {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
}
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const result = wavStreamPlayer.value.analyser
|
||||
? wavStreamPlayer.value.getFrequencies('voice')
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, '#009900', 10, 0, 8);
|
||||
? wavStreamPlayer.value.getFrequencies("voice")
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, "#009900", 10, 0, 8);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
@@ -261,17 +266,17 @@ const initialize = async () => {
|
||||
};
|
||||
render();
|
||||
|
||||
client.value.on('error', (event) => {
|
||||
showMessageError(event.error)
|
||||
client.value.on("error", (event) => {
|
||||
showMessageError(event.error);
|
||||
});
|
||||
|
||||
client.value.on('realtime.event', (re) => {
|
||||
if (re.event.type === 'error') {
|
||||
showMessageError(re.event.error)
|
||||
client.value.on("realtime.event", (re) => {
|
||||
if (re.event.type === "error") {
|
||||
showMessageError(re.event.error);
|
||||
}
|
||||
});
|
||||
|
||||
client.value.on('conversation.interrupted', async () => {
|
||||
client.value.on("conversation.interrupted", async () => {
|
||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||
if (trackSampleOffset?.trackId) {
|
||||
const { trackId, offset } = trackSampleOffset;
|
||||
@@ -279,21 +284,20 @@ const initialize = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
client.value.on('conversation.updated', async ({ item, delta }) => {
|
||||
client.value.on("conversation.updated", async ({ item, delta }) => {
|
||||
// console.log('item updated', item, delta)
|
||||
if (delta?.audio) {
|
||||
wavStreamPlayer.value.add16BitPCM(delta.audio, item.id);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const voiceInterval = ref(null);
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
initialize();
|
||||
// 启动聊天进行中的动画
|
||||
voiceInterval.value = setInterval(animateVoice, 200);
|
||||
typeText()
|
||||
typeText();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -304,32 +308,31 @@ onUnmounted(() => {
|
||||
// 挂断通话
|
||||
const hangUp = async () => {
|
||||
try {
|
||||
isConnected.value = false
|
||||
isConnected.value = false;
|
||||
// 停止播放拨号音乐
|
||||
if (backgroundAudio.value?.currentTime) {
|
||||
backgroundAudio.value?.pause()
|
||||
backgroundAudio.value.currentTime = 0
|
||||
backgroundAudio.value?.pause();
|
||||
backgroundAudio.value.currentTime = 0;
|
||||
}
|
||||
// 断开客户端的连接
|
||||
client.value.reset()
|
||||
client.value.reset();
|
||||
// 中断语音输入和输出服务
|
||||
await wavRecorder.value.end()
|
||||
await wavStreamPlayer.value.interrupt()
|
||||
await wavRecorder.value.end();
|
||||
await wavStreamPlayer.value.interrupt();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
} finally {
|
||||
// 播放挂断音乐
|
||||
hangUpAudio.value?.play()
|
||||
emits('close')
|
||||
hangUpAudio.value?.play();
|
||||
emits("close");
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
defineExpose({ connect,hangUp });
|
||||
defineExpose({ connect, hangUp });
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
||||
@import "@/assets/css/realtime.styl"
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
<div class="running-job-box" v-if="list.length > 0">
|
||||
<div class="job-item" v-for="item in list" :key="item.id">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
|
||||
<div class="progress" v-if="item.progress > 0">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
|
||||
<div class="progress" v-if="item.progress > 0">
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="item.progress"
|
||||
:width="100"
|
||||
color="#47fff1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
@@ -20,21 +22,22 @@
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" v-else :image="nodata" description="暂无任务" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default:[],
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
@import "~@/assets/css/running-job-list.styl"
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,95 +1,111 @@
|
||||
<template>
|
||||
<div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
|
||||
<div
|
||||
class="user-bill"
|
||||
v-loading="loading"
|
||||
element-loading-background="rgba(255,255,255,.3)"
|
||||
>
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
--el-table-tr-bg-color:#2D323B;
|
||||
--el-table-row-hover-bg-color:#373C47;
|
||||
--el-table-header-bg-color:#474E5C;
|
||||
--el-table-text-color:#d1d1d1">
|
||||
<el-table
|
||||
:data="items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="order_no" label="订单号">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.order_no }}</span>
|
||||
<el-icon class="copy-order-no" :data-clipboard-text="scope.row.order_no">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-order-no"
|
||||
:data-clipboard-text="scope.row.order_no"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="subject" label="产品名称"/>
|
||||
<el-table-column prop="amount" label="订单金额"/>
|
||||
<el-table-column prop="subject" label="产品名称" />
|
||||
<el-table-column prop="amount" label="订单金额" />
|
||||
<el-table-column label="订单算力">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.remark?.power }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pay_method" label="支付渠道"/>
|
||||
<el-table-column prop="pay_name" label="支付名称"/>
|
||||
<el-table-column prop="pay_method" label="支付渠道" />
|
||||
<el-table-column prop="pay_name" label="支付名称" />
|
||||
<el-table-column label="支付时间">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row['pay_time']">{{ dateFormat(scope.row['pay_time']) }}</span>
|
||||
<span v-if="scope.row['pay_time']">{{
|
||||
dateFormat(scope.row["pay_time"])
|
||||
}}</span>
|
||||
<el-tag v-else>未支付</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" v-else />
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="total > 0" background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
@current-change="fetchData()"
|
||||
:total="total"/>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
@current-change="fetchData()"
|
||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import { DocumentCopy } from "@element-plus/icons-vue";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(12)
|
||||
const loading = ref(true)
|
||||
const items = ref([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(12);
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
const clipboard = new Clipboard('.copy-order-no');
|
||||
clipboard.on('success', () => {
|
||||
fetchData();
|
||||
const clipboard = new Clipboard(".copy-order-no");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.page_size
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
})
|
||||
}
|
||||
httpGet("/api/order/list", { page: page.value, page_size: pageSize.value })
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items;
|
||||
total.value = res.data.total;
|
||||
page.value = res.data.page;
|
||||
pageSize.value = res.data.page_size;
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
.user-bill {
|
||||
background-color: var(--chat-bg);
|
||||
|
||||
.pagination {
|
||||
margin: 20px 0 0 0;
|
||||
display: flex;
|
||||
@@ -105,4 +121,4 @@ const fetchData = () => {
|
||||
color #20a0ff
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,77 +3,92 @@
|
||||
<el-form :model="user" label-width="100px">
|
||||
<el-row>
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".png,.jpg,.jpeg,.bmp"
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".png,.jpg,.jpeg,.bmp"
|
||||
>
|
||||
<el-avatar v-if="user.avatar" :src="user.avatar" shape="circle" :size="100"/>
|
||||
<el-avatar
|
||||
v-if="user.avatar"
|
||||
:src="user.avatar"
|
||||
shape="circle"
|
||||
:size="100"
|
||||
/>
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="user['nickname']"/>
|
||||
<el-input v-model="user['nickname']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="账号">
|
||||
<span>{{ user.username }}</span>
|
||||
<el-tooltip
|
||||
<div class="flex">
|
||||
<span>{{ user.username }}</span>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="您已经是 VIP 会员"
|
||||
placement="right"
|
||||
>
|
||||
<span class="vip-icon"><el-image v-if="user.vip" :src="vipImg" style="height: 25px;margin-left: 10px"/></span>
|
||||
</el-tooltip>
|
||||
>
|
||||
<span class="vip-icon"
|
||||
><el-image
|
||||
v-if="user.vip"
|
||||
:src="vipImg"
|
||||
style="height: 25px; margin-left: 10px"
|
||||
/></span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余算力">
|
||||
<el-tag>{{ user['power'] }}</el-tag>
|
||||
<el-text type="warning">{{ user["power"] }}</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
|
||||
<el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
|
||||
<el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
|
||||
<el-tag type="danger">{{ dateFormat(user["expired_time"]) }}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<el-row class="opt-line">
|
||||
<el-button color="#47fff1" :dark="false" @click="save">保存</el-button>
|
||||
<el-button :dark="false" type="primary" @click="save">保存</el-button>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Plus} from "@element-plus/icons-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import Compressor from "compressorjs";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {checkSession} from "@/store/cache";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import { checkSession } from "@/store/cache";
|
||||
|
||||
const user = ref({
|
||||
vip: false,
|
||||
username: '演示数据',
|
||||
nickname: '演示数据',
|
||||
avatar: '/images/vip.png',
|
||||
mobile: '演示数据',
|
||||
power: 99999,
|
||||
})
|
||||
const vipImg = ref("/images/vip.png")
|
||||
username: "演示数据",
|
||||
nickname: "演示数据",
|
||||
avatar: "/images/menu/member.png",
|
||||
mobile: "演示数据",
|
||||
power: 99999
|
||||
});
|
||||
const vipImg = ref("/images/menu/member.png");
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(() => {
|
||||
// 获取最新用户信息
|
||||
httpGet('/api/user/profile').then(res => {
|
||||
user.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取用户信息失败:" + e.message)
|
||||
checkSession()
|
||||
.then(() => {
|
||||
// 获取最新用户信息
|
||||
httpGet("/api/user/profile")
|
||||
.then((res) => {
|
||||
user.value = res.data;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取用户信息失败:" + e.message);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
const afterRead = (file) => {
|
||||
// 压缩图片并上传
|
||||
@@ -81,28 +96,32 @@ const afterRead = (file) => {
|
||||
quality: 0.6,
|
||||
success(result) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', result, result.name);
|
||||
formData.append("file", result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
user.value.avatar = res.data.url
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
})
|
||||
httpPost("/api/upload", formData)
|
||||
.then((res) => {
|
||||
user.value.avatar = res.data.url;
|
||||
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("图片上传失败:" + e.message);
|
||||
});
|
||||
},
|
||||
error(err) {
|
||||
console.log(err.message);
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
httpPost('/api/user/profile/update', user.value).then(() => {
|
||||
ElMessage.success({message: '更新成功', duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('更新失败:' + e.message)
|
||||
})
|
||||
}
|
||||
httpPost("/api/user/profile/update", user.value)
|
||||
.then(() => {
|
||||
ElMessage.success({ message: "更新成功", duration: 500 });
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("更新失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -127,4 +146,4 @@ const save = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<div class="container">
|
||||
<h1 class="title">{{ title }}-{{ version }}</h1>
|
||||
<h2 class="title">{{ title }}-{{ version }}</h2>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
@@ -128,10 +128,11 @@ const send = (text) => {
|
||||
width 100%
|
||||
|
||||
.title {
|
||||
font-size: 2.25rem
|
||||
// font-size: 2.25rem
|
||||
line-height: 2.5rem
|
||||
font-weight 600
|
||||
margin-bottom: 4rem
|
||||
color var( --theme-textcolor-normal)
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
<template>
|
||||
<div class="black-input-wrapper">
|
||||
<el-input v-model="model" :type="type" :rows="rows"
|
||||
@input="onInput"
|
||||
style="--el-input-bg-color:#252020;
|
||||
--el-input-border-color:#414141;
|
||||
--el-input-focus-border-color:#414141;
|
||||
--el-text-color-regular: #f1f1f1;
|
||||
--el-input-border-radius: 10px;
|
||||
--el-border-color-hover:#616161"
|
||||
resize="none"
|
||||
:placeholder="placeholder" :maxlength="maxlength"/>
|
||||
<el-input
|
||||
v-model="model"
|
||||
:type="type"
|
||||
:rows="rows"
|
||||
@input="onInput"
|
||||
resize="none"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
/>
|
||||
<div class="word-stat" v-if="rows > 1">
|
||||
<span>{{value.length}}</span>/<span>{{maxlength}}</span>
|
||||
<span>{{ value.length }}</span
|
||||
>/<span>{{ maxlength }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref, watch} from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
value : {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'input',
|
||||
default: "input"
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 1024
|
||||
}
|
||||
});
|
||||
watch(() => props.value, (newValue) => {
|
||||
model.value = newValue
|
||||
})
|
||||
const model = ref(props.value)
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
model.value = newValue;
|
||||
}
|
||||
);
|
||||
const model = ref(props.value);
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['update:value']);
|
||||
const emits = defineEmits(["update:value"]);
|
||||
const onInput = (value) => {
|
||||
emits('update:value',value)
|
||||
}
|
||||
emits("update:value", value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@@ -77,4 +78,4 @@ const onInput = (value) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
<template>
|
||||
|
||||
<el-select v-model="model" :placeholder="placeholder"
|
||||
:value="value" @change="$emit('update:value', $event)"
|
||||
style="--el-fill-color-blank:#252020;
|
||||
--el-text-color-regular: #a1a1a1;
|
||||
--el-select-disabled-color:#0E0808;
|
||||
--el-color-primary-light-9:#0E0808;
|
||||
--el-border-radius-base:20px;
|
||||
--el-border-color:#0E0808;">
|
||||
<el-option v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
<el-select
|
||||
v-model="model"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="$emit('update:value', $event)"
|
||||
style="--el-border-radius-base: 20px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BlackSelect',
|
||||
name: "BlackSelect",
|
||||
props: {
|
||||
value : {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
default: "请选择"
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
@@ -37,7 +36,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
model: this.value
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<template>
|
||||
|
||||
<el-switch v-model="model" :size="size"
|
||||
@change="$emit('update:value', $event)"
|
||||
style="--el-switch-on-color:#555555;--el-color-white:#0E0808"/>
|
||||
|
||||
<el-switch
|
||||
v-model="model"
|
||||
:size="size"
|
||||
@change="$emit('update:value', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref, watch} from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
const props = defineProps({
|
||||
value : Boolean,
|
||||
value: Boolean,
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
default: "default"
|
||||
}
|
||||
});
|
||||
const model = ref(props.value)
|
||||
const model = ref(props.value);
|
||||
|
||||
watch(() => props.value, (newValue) => {
|
||||
model.value = newValue
|
||||
})
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
model.value = newValue;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user