Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev

This commit is contained in:
xiaojunnuo
2026-02-03 09:50:29 +08:00
41 changed files with 379 additions and 96 deletions
+5 -1
View File
@@ -8,19 +8,23 @@
</template>
<script lang="ts" setup>
import { computed, provide, ref } from "vue";
import { computed, provide, Ref, ref } from "vue";
import { preferences, usePreferences } from "/@/vben/preferences";
import { useAntdDesignTokens } from "/@/vben/hooks";
import { Modal, theme } from "ant-design-vue";
import AConfigProvider from "ant-design-vue/es/config-provider";
import { antdvLocale } from "./locales/antdv";
import { setI18nLanguage } from "/@/locales";
import { mitter } from "./utils/util.mitt";
defineOptions({
name: "App",
});
const [modal, contextHolder] = Modal.useModal();
provide("modal", modal);
mitter.on("getModal", (event: any) => {
event.ModalRef = modal;
});
const locale = preferences.app.locale;
setI18nLanguage(locale);
@@ -43,6 +43,7 @@ type Text = {
};
const text = computed<Text>(() => {
const vipLabel = settingStore.vipLabel;
const plusMessage = settingStore.plusInfo?.message;
const map = {
isComm: {
comm: {
@@ -95,7 +96,7 @@ const text = computed<Text>(() => {
},
nav: {
name: t("vip.free.nav.name"),
title: t("vip.free.nav.title"),
title: plusMessage || t("vip.free.nav.title"),
},
},
};
@@ -1,9 +1,24 @@
<template>
<div class="mt-10 mb-10 vip-active-modal">
<div v-if="productInfo.notice" class="mb-10">
<div class="mt-10 vip-active-modal">
<div v-if="todayOrderCount.enabled" class="order-count hidden md:flex">
<div v-for="(stage, index) in todayOrderCount.stages" :key="index" class="status-item" :class="{ 'status-show': TodayVipOrderCountRef.current === index }">
<div class="background">
<img :src="stage.bg" alt="" />
</div>
<div class="flex flex-col order-count-text weight-bold">
<div class="count-text ml-4 flex items-center">
<fs-icon icon="noto:fire" class="fs-20 mr-2"></fs-icon>
<span> 今日赞助 </span>
<span class="count-number color-red font-bold text-2xl ml-1 mr-1"> {{ stage.orderCount }} </span>
<span> {{ stage.title }} </span>
</div>
</div>
</div>
</div>
<div v-if="productInfo.notice" class="mt-10">
<a-alert type="error" :message="productInfo.notice"></a-alert>
</div>
<div class="vip-type-vs">
<div class="vip-type-vs mt-10">
<a-row :gutter="20">
<div v-for="(item, key) in vipTypeDefine" :key="key" class="w-full md:w-1/3 mb-4 p-5">
<div :class="`vip-block ${key === settingStore.plusInfo.vipType ? 'current' : ''}`">
@@ -94,13 +109,14 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from "vue";
import { computed, nextTick, onMounted, reactive, Ref, ref } from "vue";
import { message, Modal } from "ant-design-vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import { useSettingStore } from "/@/store/settings";
import * as api from "./api";
import { utils } from "/@/utils";
const { t } = useI18n();
const router = useRouter();
@@ -214,22 +230,115 @@ const vipTypeDefine: any = {
},
};
const TodayVipOrderCountRef = ref(0);
const TodayVipOrderCountRef: Ref = ref({});
async function getTodayVipOrderCount() {
const res = await api.getTodayVipOrderCount();
if (res) {
TodayVipOrderCountRef.value = res.count;
TodayVipOrderCountRef.value = res;
}
}
const todayOrderCount = computed(() => {
const countInfo = TodayVipOrderCountRef.value;
const enabled = countInfo?.enabled || false;
const orderCount = countInfo?.orderCount || 0;
for (const stage of countInfo?.stages) {
stage.orderCount = stage.countGe || 0;
}
const lastStage = countInfo?.stages?.[countInfo?.stages?.length - 1] || {};
lastStage.orderCount = orderCount;
return {
enabled: enabled,
orderCount: orderCount,
title: lastStage.title || "",
stages: countInfo?.stages,
};
});
async function scrollOrderCount() {
const stages = todayOrderCount.value.stages;
if (!stages.length) {
return;
}
let index = 0;
for (const stage of stages) {
TodayVipOrderCountRef.value.current = index;
await utils.sleep(500);
index++;
}
}
onMounted(async () => {
getTodayVipOrderCount();
await getTodayVipOrderCount();
await scrollOrderCount();
});
</script>
<style lang="less">
.vip-active-modal {
.order-count {
height: 80px;
position: relative;
border: 1px solid #fee2c5;
border-radius: 5px;
.background {
border: 0px;
border-radius: 5px;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 0;
display: flex;
justify-content: flex-end;
overflow: hidden;
img {
height: 100%;
object-fit: cover;
}
}
.order-count-text {
position: absolute;
width: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
/* 左至右渐变*/
background: linear-gradient(to right, rgba(255, 217, 167, 0.5), rgba(255, 255, 255, 0));
.count-text {
font-size: 16px;
font-weight: 600;
color: #ff6600;
display: flex;
.count-number {
margin-bottom: 5px;
}
}
}
.status-item {
opacity: 0;
transition: all 0.7s ease-in-out;
}
.status-show {
opacity: 1;
}
}
.vip-block {
display: flex;
flex-direction: column;
@@ -27,6 +27,7 @@ export type PlusInfo = {
expireTime?: number;
isPlus: boolean;
isComm?: boolean;
message?: string;
};
export type SysPublicSetting = {
registerEnabled?: boolean;
@@ -107,6 +108,8 @@ export type SysPrivateSetting = {
};
export type SysInstallInfo = {
siteId: string;
bindUrl?: string;
bindUrl2?: string;
};
export type MenuItem = {
id: string;
@@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { Modal, notification } from "ant-design-vue";
import { notification } from "ant-design-vue";
import * as basicApi from "./api.basic";
import { AppInfo, HeaderMenus, PlusInfo, SiteEnv, SiteInfo, SuiteSetting, SysInstallInfo, SysPublicSetting } from "./api.basic";
import { useUserStore } from "../user";
@@ -20,6 +20,7 @@ export interface SettingState {
installTime?: number;
bindUserId?: number;
bindUrl?: string;
bindUrl2?: string;
accountServerBaseUrl?: string;
appKey?: string;
};
@@ -153,9 +154,11 @@ export const useSettingStore = defineStore({
if (this.plusInfo?.expireTime === -1) {
return "永久";
}
//@ts-ignore
return dayjs(this.plusInfo?.expireTime).format("YYYY-MM-DD");
},
isForever() {
//@ts-ignore
return this.isPlus && this.plusInfo?.expireTime === -1;
},
vipLabel(): string {
@@ -251,9 +254,17 @@ export const useSettingStore = defineStore({
url = url.split("#")[0];
return url;
},
async doBindUrl() {
const url = this.getBaseUrl();
await basicApi.bindUrl({ url });
async doBindUrl(key: string = "url") {
const url = this.installInfo.bindUrl;
const url2 = this.installInfo.bindUrl2;
const thisUrl = this.getBaseUrl();
const form = {
url,
url2,
[key]: thisUrl,
};
await basicApi.bindUrl(form);
await this.loadSysSettings();
},
async checkUrlBound() {
@@ -262,24 +273,64 @@ export const useSettingStore = defineStore({
if (!userStore.isAdmin) {
return;
}
const event: any = { ModalRef: null };
mitter.emit("getModal", event);
const Modal = event.ModalRef;
let modalRef: any = null;
const bindUrl = this.installInfo.bindUrl;
const bindUrl2 = this.installInfo.bindUrl2;
const doBindRequest = async (key: string) => {
await this.doBindUrl(key);
if (modalRef) {
modalRef.destroy();
}
};
if (!bindUrl) {
//绑定url
await this.doBindUrl();
await this.doBindUrl("url");
} else {
//检查当前url 是否与绑定的url一致
const url = window.location.href;
if (!url.startsWith(bindUrl)) {
Modal.confirm({
title: "URL地址有变化",
content: "以后都用这个新地址访问本系统吗?",
onOk: async () => {
await this.doBindUrl();
if (!url.startsWith(bindUrl) && !url.startsWith(bindUrl2)) {
modalRef = Modal.warning({
title: "URL地址未绑定,是否绑定此地址?",
width: 500,
keyboard: false,
content: () => {
return (
<div class="p-4">
<div class="flex items-center justify-between">
<span>
1
<a-tag color="green">{bindUrl || "未占用"}</a-tag>
</span>
<a-button type="primary" onClick={() => doBindRequest("url")}>
1
</a-button>
</div>
<div class="flex items-center justify-between mt-3">
<span>
2
<a-tag color="green">{bindUrl2 || "未占用"}</a-tag>
</span>
<a-button type="primary" onClick={() => doBindRequest("url2")}>
2
</a-button>
</div>
</div>
);
},
okText: "是的,继续",
cancelText: "不是,回到原来的地址",
onOk: async () => {
// await this.doBindUrl();
window.location.href = bindUrl;
},
okButtonProps: {
danger: true,
},
okText: "不,回到原来的地址",
cancelText: "不,回到原来的地址",
onCancel: () => {
window.location.href = bindUrl;
},
+16 -1
View File
@@ -320,10 +320,25 @@ h6 {
font-size: 16px !important;
}
.fs-18 {
font-size: 18px !important;
}
.fs-20 {
font-size: 20px !important;
}
.fs-24 {
font-size: 24px !important;
}
.fs-26 {
font-size: 26px !important;
}
.fs-28 {
font-size: 28px !important;
}
.fs-32 {
font-size: 32px !important;
}
.w-50\% {
width: 50%;
}