Browse Source

拉取master到开发分支

master^2
宋杰 2 days ago
parent
commit
a2c4644afd
  1. 2
      src/api/AIxiaocaishen.js
  2. BIN
      src/assets/fonts/方正新综艺简体.ttf
  3. BIN
      src/assets/img/AiEmotion/标题.png
  4. BIN
      src/assets/img/AiEmotion/角标.png
  5. BIN
      src/assets/img/AiEmotion/锁定.png
  6. 190
      src/views/AIchat.vue
  7. 8
      src/views/AiEmotion.vue
  8. 118
      src/views/Announcement.vue
  9. 883
      src/views/components/marketTemperature.vue

2
src/api/AIxiaocaishen.js

@ -208,6 +208,8 @@ export const getMarketAndCodeAPI = function (params) {
method: "POST", method: "POST",
data: new URLSearchParams(params), data: new URLSearchParams(params),
headers: { headers: {
token:
"pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
}); });

BIN
src/assets/fonts/方正新综艺简体.ttf

BIN
src/assets/img/AiEmotion/标题.png

After

Width: 330  |  Height: 39  |  Size: 7.4 KiB

BIN
src/assets/img/AiEmotion/角标.png

After

Width: 109  |  Height: 91  |  Size: 7.6 KiB

BIN
src/assets/img/AiEmotion/锁定.png

After

Width: 33  |  Height: 32  |  Size: 644 B

190
src/views/AIchat.vue

@ -330,40 +330,45 @@ const isTypingInProgress = ref(false);
const createTypingEffect = (message, content, speed) => { const createTypingEffect = (message, content, speed) => {
return new Promise((resolve) => { return new Promise((resolve) => {
chatStore.messages.push(message); chatStore.messages.push(message);
if (content != "") {
let index = 0;
if (Array.isArray(content) && content.length > 0) {
message.content = ""; message.content = "";
message.isTyping = true; message.isTyping = true;
const typingInterval = setInterval(() => {
if (index < content.length) {
message.content += content.charAt(index);
index++;
} else {
clearInterval(typingInterval);
message.isTyping = false;
let currentIndex = 0;
// KaTeX
const processNextElement = () => {
if (currentIndex >= content.length) {
message.isTyping = false;
nextTick(() => { nextTick(() => {
if (message.content.includes("$$")) {
message.content = message.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
}
resolve(); // resolve resolve(); // resolve
}); });
return;
} }
}, speed);
if (currentIndex % 2 === 0) {
//
message.content += content[currentIndex];
currentIndex++;
processNextElement(); //
} else {
//
const text = content[currentIndex];
let charIndex = 0;
const typingInterval = setInterval(() => {
if (charIndex < text.length) {
message.content += text.charAt(charIndex);
charIndex++;
} else {
clearInterval(typingInterval);
currentIndex++;
processNextElement(); //
}
}, speed);
}
};
processNextElement(); //
} else { } else {
if (message.kline) { if (message.kline) {
if (message.klineType == 1) { if (message.klineType == 1) {
@ -572,8 +577,11 @@ watch(
}; };
try { try {
const env = import.meta.env.VITE_ENV;
const result20 = await dataListAPI({ const result20 = await dataListAPI({
token: localStorage.getItem("localToken"),
token:
'8Csj5VVX1UbIb4C3oxrnbZi0+fEeMx8pywnIlrmTm45Cb/EllzWACLto9J9+fCFsfdgBOvKvyY94FvqlvM0',
// "8nkj4QBV1RPIb4CzoRTnbZi0+fEeMx8pywnIlrmTxdwROKkuwWqAWu9orpkpeXVqL98DPfeonNYpHv+mucA",
market: codeData.value.market, market: codeData.value.market,
code: codeData.value.code, code: codeData.value.code,
language: "cn", //t.value.suoxie, language: "cn", //t.value.suoxie,
@ -743,7 +751,7 @@ watch(
class: "title1", class: "title1",
type: "title1", type: "title1",
content: codeData.value.name + "全景作战报告", content: codeData.value.name + "全景作战报告",
date: moment().format("MM/DD/YYYY"),
date: moment().format("DD/MM/YYYY"),
}, },
"", "",
50 50
@ -796,7 +804,7 @@ watch(
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage1, ac1, 50);
addTypingTask(aiMessage1, ["", ac1], 50);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -1030,7 +1038,7 @@ watch(
// aiMessage2.isTyping = false; // aiMessage2.isTyping = false;
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage2, ac2, 50);
addTypingTask(aiMessage2, ["", ac2], 50);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -1087,6 +1095,8 @@ watch(
// } // }
// } // }
// ); // );
const ac31 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【主力行为】</p><p>`;
const ac32 = `${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`;
const ac3 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【主力行为】</p><p>${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`; const ac3 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【主力行为】</p><p>${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`;
// //
@ -1110,7 +1120,7 @@ watch(
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage3, ac3, 50);
addTypingTask(aiMessage3, [ac31, ac32], 50);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -1142,6 +1152,15 @@ watch(
const nengliang = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【能量维度】</p><p>${result23.data.nengliang}</p>`; const nengliang = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【能量维度】</p><p>${result23.data.nengliang}</p>`;
const ac4 = kongjian + shijian + nengliang; const ac4 = kongjian + shijian + nengliang;
const ac41 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【空间维度】</p><p style="display:flex;justify-content:center;">`;
const ac42 = `${arr[0]},${arr[1]}`;
const ac43 = `</p><p style="display:flex;justify-content:center;">`;
const ac44 = `${arr[2]},${arr[3]}</p>`;
const ac45 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【时间维度】</p><p style="display:flex;justify-content:center;">`;
const ac46 = `${result23.data.shijian}</p>`;
const ac47 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【能量维度】</p><p>`;
const ac48 = `${result23.data.nengliang}</p>`;
// const pc4 = marked( // const pc4 = marked(
// kongjian + // kongjian +
// "\n" + // "\n" +
@ -1179,7 +1198,11 @@ watch(
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage4, ac4, 50);
addTypingTask(
aiMessage4,
[ac41, ac42, ac43, ac44, ac45, ac46, ac47, ac48],
50
);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -1227,7 +1250,10 @@ watch(
const cftj = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【触发条件】</p><p>${result24.data.cftl}</p>`; const cftj = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【触发条件】</p><p>${result24.data.cftl}</p>`;
const gfzl = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【攻防指令】</p><p>${result24.data.gfzl}</p>`; const gfzl = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【攻防指令】</p><p>${result24.data.gfzl}</p>`;
const ac5 = cftj + gfzl; const ac5 = cftj + gfzl;
const ac51 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【触发条件】</p><p>`;
const ac52 = `${result24.data.cftl}</p>`;
const ac53 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【攻防指令】</p><p>`;
const ac54 = `${result24.data.gfzl}</p>`;
// const pc5 = marked(result24.data.cftl + "/n" + result24.data.gfzl); // const pc5 = marked(result24.data.cftl + "/n" + result24.data.gfzl);
// const ac5 = pc5.replace(katexRegex, (match, formula) => { // const ac5 = pc5.replace(katexRegex, (match, formula) => {
// try { // try {
@ -1259,7 +1285,7 @@ watch(
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage5, ac5, 50);
addTypingTask(aiMessage5, [ac51, ac52, ac53, ac54], 50);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -1268,7 +1294,7 @@ watch(
// content: ac5, // content: ac5,
// }); // });
const ac6 = "内容由AI生成,请注意甄别";
const ac6 = "内容由AI生成,请注意甄别";
// //
const aiMessage6 = reactive({ const aiMessage6 = reactive({
sender: "ai", sender: "ai",
@ -1290,7 +1316,7 @@ watch(
// } // }
// }, 50); // 50ms/ // }, 50); // 50ms/
addTypingTask(aiMessage6, ac6, 100);
addTypingTask(aiMessage6, ["", ac6], 100);
// chatStore.messages.push({ // chatStore.messages.push({
// sender: "ai", // sender: "ai",
@ -2286,7 +2312,7 @@ function KlineCanvsEcharts(containerId) {
fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8), fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8),
}, },
orient: "horizontal", orient: "horizontal",
top: window.innerWidth > 768 ? "72%" : "64%",
top: window.innerWidth > 768 ? "68%" : "64%",
width: "100%", width: "100%",
left: "center", left: "center",
itemGap: 15, itemGap: 15,
@ -2389,23 +2415,53 @@ function KlineCanvsEcharts(containerId) {
}, },
grid: [ grid: [
{ {
// left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "10%" : "5%",
height: window.innerWidth > 768 ? "36%" : "34%",
left:
window.innerWidth > 1024
? "70vw"
: window.innerWidth > 768
? "65vw"
: "55vw",
right:
window.innerWidth > 1024
? "40vw"
: window.innerWidth > 768
? "30vw"
: "40vw",
top: window.innerWidth > 768 ? "8%" : "5%",
height: window.innerWidth > 768 ? "34%" : "34%",
containLabel: false, containLabel: false,
}, },
{ {
// left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "50%" : "42%",
height: window.innerWidth > 768 ? "20%" : "22%",
left:
window.innerWidth > 1024
? "70vw"
: window.innerWidth > 768
? "65vw"
: "55vw",
right:
window.innerWidth > 1024
? "40vw"
: window.innerWidth > 768
? "30vw"
: "40vw",
top: window.innerWidth > 768 ? "45%" : "42%",
height: window.innerWidth > 768 ? "22%" : "22%",
containLabel: false, containLabel: false,
}, },
{ {
// left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "78%" : "70%",
left:
window.innerWidth > 1024
? "70vw"
: window.innerWidth > 768
? "65vw"
: "55vw",
right:
window.innerWidth > 1024
? "40vw"
: window.innerWidth > 768
? "30vw"
: "40vw",
top: window.innerWidth > 768 ? "73%" : "70%",
height: window.innerWidth > 768 ? "20%" : "22%", height: window.innerWidth > 768 ? "20%" : "22%",
containLabel: false, containLabel: false,
}, },
@ -2547,7 +2603,8 @@ function KlineCanvsEcharts(containerId) {
xAxisIndex: [0, 1, 2], xAxisIndex: [0, 1, 2],
type: "slider", type: "slider",
top: window.innerWidth > 768 ? "95%" : "96%", top: window.innerWidth > 768 ? "95%" : "96%",
left: window.innerWidth > 768 ? "10%" : "8%",
// left: window.innerWidth > 768 ? "10%" : "8%",
// right: window.innerWidth > 768 ? "4%" : "8%",
start: 98, start: 98,
end: 100, end: 100,
}, },
@ -2885,12 +2942,12 @@ function KlineCanvsEcharts(containerId) {
symbol: "rect", symbol: "rect",
symbolSize: (value, params) => { symbolSize: (value, params) => {
const width = window.innerWidth; const width = window.innerWidth;
const baseHeight = 36;
if (width <= 375) {
return [2, 16];
} else if (width <= 768) {
return [2, 24];
}
const baseHeight = 22;
// if (width <= 375) {
// return [2, 16];
// } else if (width <= 768) {
// return [2, 22];
// }
return [2, baseHeight]; return [2, baseHeight];
}, },
itemStyle: { itemStyle: {
@ -2906,7 +2963,7 @@ function KlineCanvsEcharts(containerId) {
if (item[1] === 0) { if (item[1] === 0) {
return { return {
coord: [item[0], 20], coord: [item[0], 20],
symbolOffset: window.innerWidth > 768 ? [0, 20] : [0, 12],
symbolOffset: [0, 10],
itemStyle: { itemStyle: {
color: "#00ff00", color: "#00ff00",
}, },
@ -2938,12 +2995,12 @@ function KlineCanvsEcharts(containerId) {
symbol: "rect", symbol: "rect",
symbolSize: (value, params) => { symbolSize: (value, params) => {
const width = window.innerWidth; const width = window.innerWidth;
const baseHeight = 36;
if (width <= 375) {
return [2, 16];
} else if (width <= 768) {
return [2, 24];
}
const baseHeight = 22;
// if (width <= 375) {
// return [2, 16];
// } else if (width <= 768) {
// return [2, 24];
// }
return [2, baseHeight]; return [2, baseHeight];
}, },
itemStyle: { itemStyle: {
@ -2959,8 +3016,7 @@ function KlineCanvsEcharts(containerId) {
if (item[1] === 100) { if (item[1] === 100) {
return { return {
coord: [item[0], 80], coord: [item[0], 80],
symbolOffset:
window.innerWidth > 768 ? [0, -20] : [0, -12],
symbolOffset: [0, -10],
itemStyle: { itemStyle: {
color: "#ff0000", color: "#ff0000",
}, },
@ -3139,7 +3195,7 @@ watch(
renderAllKlineCharts(); renderAllKlineCharts();
} }
}, 300);
}, 1000);
}, },
{ immediate: true } // immediate { immediate: true } // immediate
); );
@ -3354,7 +3410,6 @@ onUnmounted(() => {
/* top: -30px; */ /* top: -30px; */
} }
.logo1 { .logo1 {
position: relative;
max-width: 350px; max-width: 350px;
min-width: 200px; min-width: 200px;
width: 25%; width: 25%;
@ -3376,7 +3431,7 @@ onUnmounted(() => {
.gif-area { .gif-area {
padding: 70px 0px; padding: 70px 0px;
/* position: relative; */
position: relative;
/* height: 30vh; */ /* height: 30vh; */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -3659,6 +3714,7 @@ onUnmounted(() => {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: calc(500px + 10vw) !important;
} }
.message-bubble.ai.content3 { .message-bubble.ai.content3 {

8
src/views/AiEmotion.vue

@ -53,7 +53,7 @@
<div class="class003"> <div class="class003">
<div class="content1"> <div class="content1">
<img class="img01" src="@/assets/img/AiEmotion/温度计.png" alt="温度计图标"> <img class="img01" src="@/assets/img/AiEmotion/温度计.png" alt="温度计图标">
<span class="title1">温度计</span>
<span class="title1">温度计</span>
</div> </div>
<div class="div00"> <div class="div00">
<div class="div01">股票温度{{ data2 ?? "NA" }}</div> <div class="div01">股票温度{{ data2 ?? "NA" }}</div>
@ -2240,6 +2240,7 @@ defineExpose({
.scaled-img { .scaled-img {
height: 300px; height: 300px;
min-height: 25rem; min-height: 25rem;
background-size: contain;
} }
} }
@ -2272,9 +2273,10 @@ defineExpose({
background-size: 100% 100%; background-size: 100% 100%;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain;
text-align: center; text-align: center;
width: 95%;
margin-top: 6%;
width:100%;
margin-top: 4%;
height: 200px; height: 200px;
min-height: 200px; min-height: 200px;
} }

118
src/views/Announcement.vue

@ -1,72 +1,73 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { getAnnouncementAPI, qsArpAamClickAPI, getMarketAndCodeAPI } from "../api/AIxiaocaishen";
import {
getAnnouncementAPI,
qsArpAamClickAPI,
getMarketAndCodeAPI,
} from "../api/AIxiaocaishen";
const marketList = ref({ const marketList = ref({
"usa": "美股",
"cn": "A股",
"hk": "港股",
"sg": "新加坡股",
"my": "马股",
"th": "泰股",
"vi": "越南股",
"can": "加拿大股"
})
usa: "美股",
cn: "A股",
hk: "港股",
sg: "新加坡股",
my: "马股",
th: "泰股",
vi: "越南股",
can: "加拿大股",
});
const codeList = ref([ const codeList = ref([
{ market: '美股', code: [] },
{ market: 'A股', code: [] },
{ market: '港股', code: [] },
{ market: '新加坡股', code: [] },
{ market: '马股', code: [] },
{ market: '泰股', code: [] },
{ market: '越南股', code: [] },
{ market: '加拿大股', code: [] },
])
{ market: "美股", code: [] },
{ market: "A股", code: [] },
{ market: "港股", code: [] },
{ market: "新加坡股", code: [] },
{ market: "马股", code: [] },
{ market: "泰股", code: [] },
{ market: "越南股", code: [] },
{ market: "加拿大股", code: [] },
]);
const getMarketAndCode = async () => { const getMarketAndCode = async () => {
const result = await getMarketAndCodeAPI()
const result = await getMarketAndCodeAPI();
// console.log(result.data, "MarketAndCode"); // console.log(result.data, "MarketAndCode");
for (let i = 0; i < result.data.length; i++) { for (let i = 0; i < result.data.length; i++) {
const item = result.data[i]; const item = result.data[i];
const market = marketList.value[item.market]; const market = marketList.value[item.market];
const codeLists=item.code.split(',')
const codeLists = item.code.split(",");
// console.log(codeLists, "codeLists",market,'market'); // console.log(codeLists, "codeLists",market,'market');
const targetMarket=codeList.value.find(item=>item.market==market)
if(targetMarket){
targetMarket.code=codeLists
const targetMarket = codeList.value.find((item) => item.market == market);
if (targetMarket) {
targetMarket.code = codeLists;
// console.log(targetMarket, "targetMarket"); // console.log(targetMarket, "targetMarket");
}else{
console.log("未找到对应的市场")
} else {
console.log("未找到对应的市场");
} }
} }
}
};
const announcementVideo = ref({}); const announcementVideo = ref({});
const getAnnouncement = async () => { const getAnnouncement = async () => {
const result = await getAnnouncementAPI()
const result = await getAnnouncementAPI();
// console.log(result.data, "result.data"); // console.log(result.data, "result.data");
announcementVideo.value.url = result.data[0].url; announcementVideo.value.url = result.data[0].url;
announcementVideo.value.img = result.data[0].img; announcementVideo.value.img = result.data[0].img;
const click = await qsArpAamClickAPI({ const click = await qsArpAamClickAPI({
token: localStorage.getItem('localToken'),
id: result.data[0].id
})
token: localStorage.getItem("localToken"),
id: result.data[0].id,
});
// console.log(click); // console.log(click);
// console.log(announcementVideo.value, "announcementVideo"); // console.log(announcementVideo.value, "announcementVideo");
}
};
const handleVideoPlay = () => { const handleVideoPlay = () => {
console.log('视频开始播放');
console.log("视频开始播放");
// //
}
};
// //
const emit = defineEmits(["updateMessage", "ensureAIchat"]); const emit = defineEmits(["updateMessage", "ensureAIchat"]);
@ -76,19 +77,25 @@ const clickCode = (code) => {
emit("updateMessage", code); emit("updateMessage", code);
emit("ensureAIchat"); emit("ensureAIchat");
}
};
onMounted(() => { onMounted(() => {
getAnnouncement()
getMarketAndCode()
})
getAnnouncement();
getMarketAndCode();
});
</script> </script>
<template> <template>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="video-container"> <div class="video-container">
<video ref="videoPlayer" :poster="announcementVideo.img" :src="announcementVideo.url" controls
class="video-player" @play="handleVideoPlay">
<video
ref="videoPlayer"
:poster="announcementVideo.img"
:src="announcementVideo.url"
controls
class="video-player"
@play="handleVideoPlay"
>
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
</div> </div>
@ -96,16 +103,25 @@ onMounted(() => {
<div class="announcement"> <div class="announcement">
<p class="announcementItem">各位AI小财神的用户,大家好!</p> <p class="announcementItem">各位AI小财神的用户,大家好!</p>
<p class="announcementItem">试运行期间,用户可在AI小财神中查看全市场数据,每个市场可查看20只股票.</p>
<p class="announcementItem">
试运行期间,用户可在AI小财神中查看全市场数据,每个市场可查看20只股票.
</p>
<p class="announcementItem">以下为各个市场可以查看的股票</p> <p class="announcementItem">以下为各个市场可以查看的股票</p>
<!-- <p class="announcementItem">历软件云版静态市场一致!</p> <!-- <p class="announcementItem">历软件云版静态市场一致!</p>
<p class="announcementItem">特此公告!</p> --> <p class="announcementItem">特此公告!</p> -->
<div v-for="(item, index) in codeList" :key="index" class="announcementItem">
<div class="announcementItem">{{ item.market }}
<span class="codeItem" v-for="(code, index) in item.code" :key="index">
<span v-if="index != 0" class="codeItem">
</span>
<div
v-for="(item, index) in codeList"
:key="index"
class="announcementItem"
>
<div class="announcementItem">
{{ item.market }}
<span
class="codeItem"
v-for="(code, index) in item.code"
:key="index"
>
<span v-if="index != 0" class="codeItem"> </span>
<span @click="clickCode(code)" class="code"> <span @click="clickCode(code)" class="code">
{{ code }} {{ code }}
</span> </span>
@ -171,7 +187,7 @@ p {
} }
.codeItem { .codeItem {
color: #4591E7;
color: #4591e7;
} }
.code { .code {

883
src/views/components/marketTemperature.vue
File diff suppressed because it is too large
View File

Loading…
Cancel
Save