diff --git a/components.d.ts b/components.d.ts
index 152f21a..0806678 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -19,6 +19,7 @@ declare module 'vue' {
QiunDataCharts: typeof import('./src/components/qiun-data-charts/qiun-data-charts.vue')['default']
QiunError: typeof import('./src/components/qiun-error/qiun-error.vue')['default']
QiunLoading: typeof import('./src/components/qiun-loading/qiun-loading.vue')['default']
+ WdActionSheet: typeof import('wot-design-uni/components/wd-action-sheet/wd-action-sheet.vue')['default']
WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default']
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCalendar: typeof import('wot-design-uni/components/wd-calendar/wd-calendar.vue')['default']
@@ -44,7 +45,6 @@ declare module 'vue' {
WdLoading: typeof import('wot-design-uni/components/wd-loading/wd-loading.vue')['default']
WdLoadmore: typeof import('wot-design-uni/components/wd-loadmore/wd-loadmore.vue')['default']
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
- WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default']
WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default']
WdPicker: typeof import('wot-design-uni/components/wd-picker/wd-picker.vue')['default']
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
diff --git a/src/components/Auth/index.vue b/src/components/Auth/index.vue
deleted file mode 100644
index 8acd07a..0000000
--- a/src/components/Auth/index.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
diff --git a/src/pages.json b/src/pages.json
index 427acf4..4d30f53 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -132,7 +132,8 @@
"type": "page",
"name": "user",
"style": {
- "navigationBarTitleText": "用户管理"
+ "navigationBarTitleText": "用户管理",
+ "backgroundColor": "#f1f5f9"
}
},
{
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index f2ae210..01e6aac 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -1,6 +1,6 @@
-
+
-
+
- {{ noticeText || "暂无通知公告" }}
+
+
+ {{ noticeText }}
+ {{ noticeText }}
+
+
+ {{ noticeText }}
@@ -49,7 +55,7 @@
- 访客数
+ 访客?/text>
@@ -61,7 +67,7 @@
- 浏览量
+ 浏览?/text>
@@ -93,8 +99,8 @@
inline
@change="handleDataRangeChange"
>
- 近7天
- 近15天
+ ??/wd-radio>
+ ?5?/wd-radio>
@@ -111,19 +117,15 @@
/**
* 首页
*
- * 功能说明:
- * - 自定义导航栏(搜索框)
- * - 轮播图展示
- * - 快捷导航入口
+ * 功能说明? * - 自定义导航栏(搜索框? * - 轮播图展? * - 快捷导航入口
* - 通知公告
* - 数据统计
* - 访问趋势图表
*
* 技术要点:
- * - 使用 useNavbar 处理自定义导航栏高度和胶囊避让;
+ * - 使用 useNavbar 处理自定义导航栏高度和胶囊避?
* - 使用 BEM 命名规范组织 CSS
- * - 支持暗黑模式(通过 CSS 变量)
- */
+ * - 支持暗黑模式(通过 CSS 变量? */
import { computed, ref } from "vue";
import { onReady } from "@dcloudio/uni-app";
@@ -154,11 +156,9 @@ interface NavItem {
const router = useRouter();
const userStore = useUserStore();
-// custom-navbar 组件内部已处理导航栏高度与胶囊避让
-
-// ============================================================================
-// 响应式数据
+// custom-navbar 组件内部已处理导航栏高度与胶囊避?
// ============================================================================
+// 响应式数?// ============================================================================
const current = ref(0);
const recentDaysRange = ref(7);
@@ -178,21 +178,19 @@ const appVersion = ref("");
const noticeList = ref([]);
const noticeText = computed(() => {
- if (!noticeList.value.length) {
- return "暂无通知";
- }
const titles = noticeList.value
.map((n: NoticePageVO) => n.title)
.filter(Boolean)
- .slice(0, 2) as string[];
- return titles.length ? titles.join(" ") : "暂无通知";
+ .slice(0, 5) as string[];
+ return titles.length ? titles.join(" ") : "";
});
+const isNoticeScrolling = computed(() => noticeList.value.length > 1);
+
// 用户权限列表
const userPerms = computed(() => userStore.userInfo?.perms || []);
-// 是否已登录
-const isLogged = computed(() => isLoggedIn());
+// 是否已登?const isLogged = computed(() => isLoggedIn());
// 检查是否有权限
const hasPermission = (perm: string) => {
@@ -214,8 +212,7 @@ const defaultNavList = computed(() => {
return result;
});
-// 快捷入口:已登录按权限过滤,未登录显示默认菜单
-const quickNavList = computed(() => {
+// 快捷入口:已登录按权限过滤,未登录显示默认菜?const quickNavList = computed(() => {
if (!isLogged.value) {
return defaultNavList.value;
}
@@ -262,7 +259,6 @@ const chartOpts = ref({
function loadAppVersion() {
try {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
const p: any = (globalThis as any).plus;
if (p?.runtime?.version) {
appVersion.value = `v${p.runtime.version}`;
@@ -279,7 +275,7 @@ async function loadNoticeData() {
return;
}
try {
- const { list } = await NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 2 });
+ const { list } = await NoticeAPI.getMyNoticePage({ pageNum: 1, pageSize: 5 });
noticeList.value = list || [];
} catch {
noticeList.value = [];
@@ -296,7 +292,9 @@ async function loadVisitStatsData() {
async function loadVisitTrendData() {
const endDate = dayjs().format("YYYY-MM-DD");
- const startDate = dayjs().subtract(recentDaysRange.value - 1, "day").format("YYYY-MM-DD");
+ const startDate = dayjs()
+ .subtract(recentDaysRange.value - 1, "day")
+ .format("YYYY-MM-DD");
try {
const data: VisitTrendVO = await LogAPI.getVisitTrend({ startDate, endDate });
@@ -304,8 +302,8 @@ async function loadVisitTrendData() {
JSON.stringify({
categories: (data.dates || []).map((d) => dayjs(d).format("MM-DD")),
series: [
- { name: "访客数(UV)", data: data.uvList || [] },
- { name: "浏览量(PV)", data: data.pvList || [] },
+ { name: "访客?UV)", data: data.uvList || [] },
+ { name: "浏览?PV)", data: data.pvList || [] },
],
})
);
@@ -319,8 +317,7 @@ async function loadVisitTrendData() {
// ============================================================================
function handleNavClick(item: NavItem) {
- // 未登录时先检查登录状态并跳转登录页
- if (!checkLogin()) {
+ // 未登录时先检查登录状态并跳转登录? if (!checkLogin()) {
return;
}
router.push({ path: item.url });
@@ -376,29 +373,28 @@ onReady(() => {
.hero {
position: relative;
}
-
.hero::after {
- content: "";
position: absolute;
- left: 0;
right: 0;
bottom: 0;
+ left: 0;
+ z-index: 1;
height: 120rpx;
+ pointer-events: none;
+ content: "";
background: linear-gradient(
to bottom,
rgba(245, 247, 250, 0) 0%,
rgba(245, 247, 250, 0.85) 60%,
rgba(245, 247, 250, 1) 100%
);
- pointer-events: none;
- z-index: 1;
}
:deep(.swiper-box) {
height: 420rpx;
overflow: hidden;
- border-bottom-left-radius: 32rpx;
border-bottom-right-radius: 32rpx;
+ border-bottom-left-radius: 32rpx;
}
:deep(.swiper-box .wd-swiper__item),
@@ -407,8 +403,7 @@ onReady(() => {
}
// ============================================================================
-// 导航栏
-// ============================================================================
+// 导航?// ============================================================================
.navbar {
position: fixed;
@@ -461,16 +456,15 @@ onReady(() => {
.section--overlay {
position: relative;
z-index: 2;
- margin-top: -140rpx;
padding: 18rpx 8rpx;
+ margin-top: -140rpx;
background: var(--color-bg);
border-radius: 24rpx;
box-shadow: 0 16rpx 36rpx rgba(0, 0, 0, 0.08);
}
// ============================================================================
-// 快捷导航项
-// ============================================================================
+// 快捷导航?// ============================================================================
.nav-item {
display: flex;
@@ -492,28 +486,27 @@ onReady(() => {
}
// ============================================================================
-// 通知栏
-// ============================================================================
+// 通知?// ============================================================================
.notice-bar {
display: flex;
align-items: center;
- background: #fff;
- border-radius: 16rpx;
padding: 24rpx 24rpx 24rpx 20rpx;
+ background: #fff;
border: 1rpx solid rgba(0, 0, 0, 0.04);
+ border-radius: 16rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
&__icon {
- flex-shrink: 0;
- width: 48rpx;
- height: 48rpx;
display: flex;
+ flex-shrink: 0;
align-items: center;
justify-content: center;
+ width: 48rpx;
+ height: 48rpx;
+ margin-right: 16rpx;
background: rgba(52, 209, 157, 0.1);
border-radius: 12rpx;
- margin-right: 16rpx;
}
&__content {
@@ -523,14 +516,35 @@ onReady(() => {
&__text {
font-size: 26rpx;
- color: #333;
font-weight: 500;
- display: -webkit-box;
- line-clamp: 1;
- -webkit-line-clamp: 1;
- -webkit-box-orient: vertical;
+ color: #333;
+ white-space: nowrap;
+ }
+
+ &__text--gap {
+ padding-left: 48rpx;
+ }
+
+ &__marquee {
+ width: 100%;
overflow: hidden;
}
+
+ &__marquee-inner {
+ display: inline-flex;
+ align-items: center;
+ white-space: nowrap;
+ animation: notice-marquee 14s linear infinite;
+ }
+}
+
+@keyframes notice-marquee {
+ 0% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(-50%);
+ }
}
// ============================================================================
@@ -544,55 +558,49 @@ onReady(() => {
}
.stat-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx 20rpx;
+ position: relative;
display: flex;
flex-direction: column;
- box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
- border: 1rpx solid rgba(0, 0, 0, 0.04);
- position: relative;
+ padding: 24rpx 20rpx;
overflow: hidden;
+ background: #fff;
+ border: 1rpx solid rgba(0, 0, 0, 0.04);
+ border-radius: 16rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
- // 背景图标容器
&__bg {
position: absolute;
right: 12rpx;
bottom: 12rpx;
width: 70rpx;
height: 70rpx;
- opacity: 0.08;
pointer-events: none;
+ opacity: 0.08;
}
- // SVG 装饰图形
&__icon {
width: 100%;
height: 100%;
- // 用户组图标 - 访客数
&--user {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%232ab789'%3E%3Ccircle cx='24' cy='16' r='8'/%3E%3Ccircle cx='10' cy='20' r='6'/%3E%3Ccircle cx='38' cy='20' r='6'/%3E%3Cpath d='M24 28c-8 0-14 4-14 8v6h28v-6c0-4-6-8-14-8z'/%3E%3Cpath d='M10 28c-4 0-8 2-8 5v5h8v-5c0-2 1-4 3-5-1 0-2 0-3 0z'/%3E%3Cpath d='M38 28c4 0 8 2 8 5v5h-8v-5c0-2-1-4-3-5 1 0 2 0 3 0z'/%3E%3C/svg%3E");
- background-size: contain;
+ background-image: url("/static/icons/visitor.svg");
background-repeat: no-repeat;
+ background-size: contain;
}
- // 图表图标 - 浏览量
&--eye {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%233a8ee6'%3E%3Crect x='4' y='24' width='8' height='20' rx='2'/%3E%3Crect x='16' y='14' width='8' height='30' rx='2'/%3E%3Crect x='28' y='20' width='8' height='24' rx='2'/%3E%3Crect x='40' y='8' width='8' height='36' rx='2'/%3E%3C/svg%3E");
- background-size: contain;
+ background-image: url("/static/icons/browser.svg");
background-repeat: no-repeat;
+ background-size: contain;
}
- // 盾牌徽章图标 - 版本
&--gear {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='%23e8a838'%3E%3Cpath d='M24 4L8 10v12c0 11 8 18 16 22 8-4 16-11 16-22V10L24 4z'/%3E%3Cpath d='M20 24l4 4 8-8' stroke='white' stroke-width='3' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
- background-size: contain;
+ background-image: url("/static/icons/setting.svg");
background-repeat: no-repeat;
+ background-size: contain;
}
}
- // 彩色卡片变体
&--green {
background: linear-gradient(135deg, #f0fdf9 0%, #fff 100%);
}
@@ -606,18 +614,18 @@ onReady(() => {
}
&__head {
+ position: relative;
+ z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
- position: relative;
- z-index: 1;
}
&__label {
font-size: 24rpx;
- color: #999;
font-weight: 500;
+ color: #999;
}
&__dot {
@@ -642,12 +650,12 @@ onReady(() => {
}
&__num {
+ position: relative;
+ z-index: 1;
font-size: 48rpx;
font-weight: 700;
line-height: 1;
letter-spacing: -1rpx;
- position: relative;
- z-index: 1;
&--green {
color: #2ab789;
@@ -666,8 +674,8 @@ onReady(() => {
}
&--full {
- grid-column: 1 / -1;
flex-direction: row;
+ grid-column: 1 / -1;
align-items: center;
justify-content: space-between;
padding: 20rpx 20rpx;
@@ -682,10 +690,10 @@ onReady(() => {
}
.stat-card__bg {
- width: 50rpx;
- height: 50rpx;
right: 16rpx;
bottom: 50%;
+ width: 50rpx;
+ height: 50rpx;
transform: translateY(50%);
}
}
diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
index d232454..239693b 100644
--- a/src/pages/login/index.vue
+++ b/src/pages/login/index.vue
@@ -9,14 +9,14 @@
- ‹
+ ?/text>
-
+
@@ -34,7 +34,7 @@
-
+
@@ -80,7 +80,7 @@
/>
-
+
- {{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
+ {{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证? }}
@@ -112,16 +112,16 @@
:disabled="loading"
@click="handleLogin"
>
- {{ loading ? "登录中..." : "登 录" }}
+ {{ loading ? "登录?.." : "?? }}
- {{ loginMode === "PASSWORD" ? "忘记密码?" : "记得密码?" }}
+ {{ loginMode === "PASSWORD" ? "忘记密码? : "记得密码? }}
- {{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
+ {{ loginMode === "PASSWORD" ? "验证码登? : "密码登录" }}
@@ -135,8 +135,7 @@
@getphonenumber="handleWechatPhoneLogin"
>
- 微信一键登录
-
+ 微信一键登?
@@ -147,7 +146,7 @@
-
+
其他登录方式
@@ -167,14 +166,10 @@
- 我已阅读并同意
-
- 《用户协议》
-
- 与
-
- 《隐私政策》
-
+ 我已阅读并同?
+ 《用户协议?
+ ?
+ 《隐私政策?
@@ -182,7 +177,7 @@
-
+
@@ -238,9 +233,8 @@
- 请阅读并同意有来技术
- 《用户协议》
- 《隐私政策》
+ 请阅读并同意有来技? 《用户协议?/text>
+ 《隐私政策?/text>
@@ -268,8 +262,7 @@ const toast = useToast();
const message = useMessage("policy-box");
const userStore = useUserStore();
-// 状态
-const loading = ref(false);
+// 状?const loading = ref(false);
const showPassword = ref(false);
const isAgreePolicy = ref(false);
const loginMode = ref<"PASSWORD" | "SMS" | "WECHAT">("PASSWORD");
@@ -283,15 +276,13 @@ const formData = ref({
captchaCode: "",
});
-// 图形验证码
-const captchaId = ref("");
+// 图形验证?const captchaId = ref("");
const captchaBase64 = ref("");
const captchaLoading = ref(false);
const redirect = ref("/pages/index/index");
-// 绑定手机号相关
-const showBindMobilePopup = ref(false);
+// 绑定手机号相?const showBindMobilePopup = ref(false);
const bindLoading = ref(false);
const bindSmsCountdown = ref(0);
const bindSmsTimer = ref | null>(null);
@@ -305,8 +296,7 @@ const bindMobileForm = ref({
const pendingLoginAction = ref<"FORM" | "WECHAT_PHONE" | null>(null);
const pendingWechatPhoneCode = ref("");
-// 获取图形验证码
-const fetchCaptcha = async () => {
+// 获取图形验证?const fetchCaptcha = async () => {
if (captchaLoading.value) return;
try {
captchaLoading.value = true;
@@ -315,17 +305,16 @@ const fetchCaptcha = async () => {
captchaId.value = res.captchaId;
captchaBase64.value = res.captchaBase64;
} catch (e) {
- console.error("获取验证码失败", e);
+ console.error("获取验证码失?, e);
} finally {
captchaLoading.value = false;
}
};
-// 计算属性
-const loginModeDesc = computed(() => {
+// 计算属?const loginModeDesc = computed(() => {
const modeMap = {
PASSWORD: "使用账号密码登录",
- SMS: "使用手机验证码登录",
+ SMS: "使用手机验证码登?,
WECHAT: "使用微信快捷登录",
};
return modeMap[loginMode.value];
@@ -362,13 +351,11 @@ onLoad((options: any) => {
// #endif
// #ifndef MP-WEIXIN
- // 非微信环境强制使用密码登录
- if (loginMode.value === "WECHAT") {
+ // 非微信环境强制使用密码登? if (loginMode.value === "WECHAT") {
loginMode.value = "PASSWORD";
}
// #endif
- // 获取图形验证码
- fetchCaptcha();
+ // 获取图形验证? fetchCaptcha();
});
onShow(() => {
@@ -425,9 +412,9 @@ const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode: string = "
async function doFormLogin() {
if (!canSubmit.value) {
if (loginMode.value === "PASSWORD") {
- toast.error("请输入用户名和密码");
+ toast.error("请输入用户名和密?);
} else if (loginMode.value === "SMS") {
- toast.error("请输入正确的手机号和验证码");
+ toast.error("请输入正确的手机号和验证?);
}
return;
}
@@ -456,8 +443,7 @@ async function doFormLogin() {
setTimeout(() => uni.reLaunch({ url: redirect.value }), 800);
} catch (error: any) {
toast.error(error?.message || "登录失败");
- // 登录失败刷新验证码
- if (loginMode.value === "PASSWORD") {
+ // 登录失败刷新验证? if (loginMode.value === "PASSWORD") {
fetchCaptcha();
}
} finally {
@@ -481,16 +467,16 @@ const handleSendCode = async () => {
return;
}
if (!isValidMobile(mobile)) {
- toast.error("请输入正确的手机号");
+ toast.error("请输入正确的手机?);
return;
}
try {
await AuthAPI.sendSmsLoginCode(mobile);
- toast.success("验证码已发送");
+ toast.success("验证码已发?);
startSmsCountdown(smsCountdown, smsTimer);
} catch (error: any) {
- toast.error(error?.message || "发送失败");
+ toast.error(error?.message || "发送失?);
}
};
@@ -574,16 +560,16 @@ const handleSendBindCode = async () => {
if (bindSmsCountdown.value > 0) return;
const mobile = bindMobileForm.value.mobile.trim();
if (!isValidMobile(mobile)) {
- toast.error("请输入正确的手机号");
+ toast.error("请输入正确的手机?);
return;
}
try {
await AuthAPI.sendSmsLoginCode(mobile);
- toast.success("验证码已发送");
+ toast.success("验证码已发?);
startSmsCountdown(bindSmsCountdown, bindSmsTimer);
} catch (error: any) {
- toast.error(error?.message || "发送失败");
+ toast.error(error?.message || "发送失?);
}
};
@@ -600,7 +586,7 @@ const handleBindMobile = async () => {
if (bindLoading.value) return;
const { mobile, code } = bindMobileForm.value;
if (!isValidMobile(mobile)) {
- toast.error("请输入正确的手机号");
+ toast.error("请输入正确的手机?);
return;
}
if (!code.trim()) {
@@ -746,8 +732,7 @@ const handleBack = () => {
letter-spacing: 0.08em;
}
-// 主内容
-.login-main {
+// 主内?.login-main {
position: relative;
z-index: 10;
display: flex;
@@ -834,8 +819,7 @@ const handleBack = () => {
}
}
-// 输入框容器
-.input-box {
+// 输入框容?.input-box {
display: flex;
align-items: center;
height: 48px;
@@ -868,8 +852,7 @@ const handleBack = () => {
padding: 8px;
}
-// 主按钮
-.btn-primary {
+// 主按?.btn-primary {
position: relative;
display: flex;
align-items: center;
@@ -967,8 +950,7 @@ const handleBack = () => {
}
}
-// 验证码按钮
-.sms-btn {
+// 验证码按?.sms-btn {
display: flex;
align-items: center;
justify-content: center;
@@ -1035,8 +1017,7 @@ const handleBack = () => {
border-bottom-color: #60a5fa;
}
-// 分割线
-.divider {
+// 分割?.divider {
display: flex;
align-items: center;
margin-top: 24px;
@@ -1094,8 +1075,7 @@ const handleBack = () => {
color: #2563eb;
}
-// 绑定手机号弹窗
-.bind-popup {
+// 绑定手机号弹?.bind-popup {
padding: 24px;
}
diff --git a/src/pages/mine/about/index.vue b/src/pages/mine/about/index.vue
index 6531362..6e60611 100644
--- a/src/pages/mine/about/index.vue
+++ b/src/pages/mine/about/index.vue
@@ -1,5 +1,5 @@
-
+