diff --git a/admin/src/main/java/org/jiayunet/admin/service/AdminLoginService.java b/admin/src/main/java/org/jiayunet/admin/service/AdminLoginService.java
index f047c38..30b11e2 100644
--- a/admin/src/main/java/org/jiayunet/admin/service/AdminLoginService.java
+++ b/admin/src/main/java/org/jiayunet/admin/service/AdminLoginService.java
@@ -5,8 +5,6 @@ import com.auth0.jwt.algorithms.Algorithm;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.jiayunet.admin.constant.AdminRedisKeyName;
-import org.jiayunet.exception.BusinessException;
-import org.jiayunet.exception.BusinessExpCodeEnum;
import org.jiayunet.mapper.AdminUserMapper;
import org.jiayunet.pojo.login.RedisLoginTokenInfo;
import org.jiayunet.pojo.po.AdminUser;
@@ -74,7 +72,7 @@ public class AdminLoginService {
// 校验密码:MD5(明文 + salt)
if (!md5(password + secret).equals(admin.getPassword())) {
handleLoginFail(username);
- throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "账号或密码错误");
+ throw new IllegalArgumentException("账号或密码错误");
}
// 登录成功,清除失败计数
diff --git a/admin/src/main/resources/static/js/api/order.js b/admin/src/main/resources/static/js/api/order.js
new file mode 100644
index 0000000..d9a11d2
--- /dev/null
+++ b/admin/src/main/resources/static/js/api/order.js
@@ -0,0 +1,13 @@
+/**
+ * 订单管理 API
+ */
+const orderApi = {
+ /** 订单分页列表 */
+ list(params) {
+ return request.post('/order/list', params);
+ },
+ /** 订单统计 */
+ stats() {
+ return request.get('/order/stats');
+ }
+};
diff --git a/admin/src/main/resources/static/js/api/product.js b/admin/src/main/resources/static/js/api/product.js
new file mode 100644
index 0000000..9d8f8ee
--- /dev/null
+++ b/admin/src/main/resources/static/js/api/product.js
@@ -0,0 +1,21 @@
+/**
+ * 商品管理 API
+ */
+const productApi = {
+ /** 商品分页列表 */
+ list(params) {
+ return request.post('/product/list', params);
+ },
+ /** 新增/编辑商品 */
+ save(data) {
+ return request.post('/product/save', data);
+ },
+ /** 上架/下架 */
+ updateStatus(id, status) {
+ return request.post('/product/updateStatus?id=' + id + '&status=' + status);
+ },
+ /** 删除商品 */
+ delete(id) {
+ return request.post('/product/delete?id=' + id);
+ }
+};
diff --git a/admin/src/main/resources/static/js/api/user.js b/admin/src/main/resources/static/js/api/user.js
new file mode 100644
index 0000000..36123dd
--- /dev/null
+++ b/admin/src/main/resources/static/js/api/user.js
@@ -0,0 +1,17 @@
+/**
+ * 用户管理 API
+ */
+const userApi = {
+ /** 用户分页列表 */
+ list(params) {
+ return request.post('/user/list', params);
+ },
+ /** 用户统计 */
+ stats() {
+ return request.get('/user/stats');
+ },
+ /** 禁用/启用用户 */
+ updateStatus(userId, status) {
+ return request.post('/user/updateStatus?userId=' + userId + '&status=' + status);
+ }
+};
diff --git a/admin/src/main/resources/static/js/request.js b/admin/src/main/resources/static/js/request.js
index 5be5d4c..ad96a66 100644
--- a/admin/src/main/resources/static/js/request.js
+++ b/admin/src/main/resources/static/js/request.js
@@ -24,12 +24,18 @@ const request = (() => {
return Promise.reject('未登录');
}
- const result = await response.json();
+ // 解析响应体
+ let result;
+ try { result = await response.json(); } catch (e) {
+ const msg = 'HTTP ' + response.status + ' 请求失败';
+ ElementPlus ? ElementPlus.ElMessage.error(msg) : alert(msg);
+ return Promise.reject(msg);
+ }
- // 业务异常
- if (result.status !== 200) {
- const msg = result.message || '请求失败';
- alert(msg);
+ // 业务异常:code 不为 "0" 表示失败
+ if (result.code !== '0') {
+ const msg = result.msg || '请求失败';
+ ElementPlus ? ElementPlus.ElMessage.error(msg) : alert(msg);
return Promise.reject(msg);
}
@@ -42,7 +48,7 @@ const request = (() => {
return http(url + query, { method: 'GET' });
},
post(url, data) {
- return http(url, { method: 'POST', body: JSON.stringify(data) });
+ return http(url, { method: 'POST', body: data != null ? JSON.stringify(data) : undefined });
},
put(url, data) {
return http(url, { method: 'PUT', body: JSON.stringify(data) });
diff --git a/admin/src/main/resources/static/js/views/dashboard.js b/admin/src/main/resources/static/js/views/dashboard.js
new file mode 100644
index 0000000..d030423
--- /dev/null
+++ b/admin/src/main/resources/static/js/views/dashboard.js
@@ -0,0 +1,58 @@
+const DashboardView = {
+ template: `
+
+
数据概览
+
+
+
+ 人
+
+
+
+
+ 人
+
+
+
+
+ 笔
+
+
+
+
+ ¥
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥
+
+
+
+
+ `,
+ setup() {
+ const { ref, onMounted, computed } = Vue;
+ const userStats = ref({ total: 0, today: 0, thisWeek: 0, thisMonth: 0 });
+ const orderStats = ref({ totalCount: 0, paidCount: 0, todayPaid: 0, weekPaid: 0, monthPaid: 0, monthIncome: 0, weekIncome: 0 });
+ const monthIncome = computed(() => (orderStats.value.monthIncome / 100).toFixed(2));
+ const weekIncome = computed(() => (orderStats.value.weekIncome / 100).toFixed(2));
+ const toNum = (obj) => { const r = {}; for (const k in obj) r[k] = Number(obj[k]); return r; };
+ onMounted(async () => {
+ try { userStats.value = toNum(await userApi.stats()); } catch(e) {}
+ try { orderStats.value = toNum(await orderApi.stats()); } catch(e) {}
+ });
+ return { userStats, orderStats, monthIncome, weekIncome };
+ }
+};
diff --git a/admin/src/main/resources/static/js/views/order.js b/admin/src/main/resources/static/js/views/order.js
new file mode 100644
index 0000000..f780bdb
--- /dev/null
+++ b/admin/src/main/resources/static/js/views/order.js
@@ -0,0 +1,77 @@
+const OrderView = {
+ template: `
+
+
+
+
+
+
+
+
+ 订单管理
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+
+
+
+
+
+
+
+ {{(row.payAmount/100).toFixed(2)}}
+
+
+ {{row.payChannel===1?'微信':'支付宝'}}
+
+
+ {{statusText(row.status)}}
+
+
+ {{formatTime(row.createTime)}}
+
+
+ {{formatTime(row.payTime)}}
+
+
+
+
+
+
+
+ `,
+ setup() {
+ const { ref, reactive, onMounted, computed } = Vue;
+ const loading = ref(false);
+ const tableData = ref([]);
+ const total = ref(0);
+ const stats = ref({ totalCount: 0, paidCount: 0, todayPaid: 0, todayIncome: 0 });
+ const todayIncome = computed(() => (stats.value.todayIncome / 100).toFixed(2));
+ const query = reactive({ pageNum: 1, pageSize: 10, mobileNumber: '', status: null, payChannel: null });
+ const loadData = async () => {
+ loading.value = true;
+ try { const res = await orderApi.list(query); tableData.value = res.list; total.value = Number(res.total); } catch(e) {}
+ finally { loading.value = false; }
+ };
+ const loadStats = async () => { try { const s = await orderApi.stats(); const r = {}; for (const k in s) r[k] = Number(s[k]); stats.value = r; } catch(e) {} };
+ const statusText = (s) => ['待支付','已支付','已退款','已关闭'][s] || '-';
+ const statusType = (s) => ['warning','success','info','danger'][s] || 'info';
+ const formatTime = (ts) => {
+ if (!ts) return '-';
+ const d = new Date(ts);
+ return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0') + ' ' + String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');
+ };
+ onMounted(() => { loadData(); loadStats(); });
+ return { loading, tableData, total, stats, todayIncome, query, loadData, statusText, statusType, formatTime };
+ }
+};
diff --git a/admin/src/main/resources/static/js/views/product.js b/admin/src/main/resources/static/js/views/product.js
new file mode 100644
index 0000000..c195933
--- /dev/null
+++ b/admin/src/main/resources/static/js/views/product.js
@@ -0,0 +1,107 @@
+const ProductView = {
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+
+
+
+
+
+ {{(row.price/100).toFixed(2)}}
+
+
+ {{row.originalPrice?(row.originalPrice/100).toFixed(2):'-'}}
+
+
+
+
+ {{row.status===1?'上架':'下架'}}
+
+
+
+ 编辑
+ {{row.status===1?'下架':'上架'}}
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 保存
+
+
+
+ `,
+ setup() {
+ const { ref, reactive, onMounted } = Vue;
+ const loading = ref(false);
+ const saving = ref(false);
+ const dialogVisible = ref(false);
+ const tableData = ref([]);
+ const total = ref(0);
+ const query = reactive({ pageNum: 1, pageSize: 10, status: null });
+ const form = reactive({ id: null, productName: '', tag: '', price: null, originalPrice: null, monthlyPrice: null, durationDays: null, sortOrder: 0, isFeatured: 0, buyButtonText: '' });
+
+ const loadData = async () => {
+ loading.value = true;
+ try { const res = await productApi.list(query); tableData.value = res.list; total.value = Number(res.total); } catch(e) {}
+ finally { loading.value = false; }
+ };
+ const openDialog = (row) => {
+ if (row) { Object.assign(form, { id: row.id, productName: row.productName, tag: row.tag, price: row.price, originalPrice: row.originalPrice, monthlyPrice: row.monthlyPrice, durationDays: row.durationDays, sortOrder: row.sortOrder, isFeatured: row.isFeatured, buyButtonText: row.buyButtonText }); }
+ else { Object.assign(form, { id: null, productName: '', tag: '', price: null, originalPrice: null, monthlyPrice: null, durationDays: null, sortOrder: 0, isFeatured: 0, buyButtonText: '' }); }
+ dialogVisible.value = true;
+ };
+ const handleSave = async () => {
+ if (!form.productName || !form.price || !form.durationDays) { ElementPlus.ElMessage.warning('请填写必填项'); return; }
+ saving.value = true;
+ try { await productApi.save(form); dialogVisible.value = false; ElementPlus.ElMessage.success('保存成功'); loadData(); } catch(e) {}
+ finally { saving.value = false; }
+ };
+ const toggleStatus = async (row) => {
+ const newStatus = row.status === 1 ? 0 : 1;
+ try { await productApi.updateStatus(row.id, newStatus); row.status = newStatus; ElementPlus.ElMessage.success('操作成功'); } catch(e) {}
+ };
+ const handleDelete = async (row) => {
+ try { await productApi.delete(row.id); ElementPlus.ElMessage.success('删除成功'); loadData(); } catch(e) {}
+ };
+ onMounted(loadData);
+ return { loading, saving, dialogVisible, tableData, total, query, form, loadData, openDialog, handleSave, toggleStatus, handleDelete };
+ }
+};
diff --git a/admin/src/main/resources/static/js/views/user.js b/admin/src/main/resources/static/js/views/user.js
new file mode 100644
index 0000000..036234e
--- /dev/null
+++ b/admin/src/main/resources/static/js/views/user.js
@@ -0,0 +1,64 @@
+const UserView = {
+ template: `
+
+
+ 用户管理
+
+
+
+
+
+
+
+
+ 查询
+
+
+
+
+
+
+
+ {{row.status===0?'正常':'禁用'}}
+
+
+ {{formatTime(row.createTime)}}
+
+
+
+
+ {{row.status===0?'禁用':'启用'}}
+
+
+
+
+
+
+
+
+
+ `,
+ setup() {
+ const { ref, reactive, onMounted } = Vue;
+ const loading = ref(false);
+ const tableData = ref([]);
+ const total = ref(0);
+ const query = reactive({ pageNum: 1, pageSize: 10, mobileNumber: '', keyword: '', status: null });
+ const loadData = async () => {
+ loading.value = true;
+ try { const res = await userApi.list(query); tableData.value = res.list; total.value = Number(res.total); } catch(e) {}
+ finally { loading.value = false; }
+ };
+ const toggleStatus = async (row) => {
+ const newStatus = row.status === 0 ? 1 : 0;
+ try { await userApi.updateStatus(row.id, newStatus); row.status = newStatus; ElementPlus.ElMessage.success('操作成功'); } catch(e) {}
+ };
+ const formatTime = (ts) => {
+ if (!ts) return '-';
+ const d = new Date(ts);
+ return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0') + ' ' + String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');
+ };
+ onMounted(loadData);
+ return { loading, tableData, total, query, loadData, toggleStatus, formatTime };
+ }
+};
diff --git a/admin/src/main/resources/templates/index.html b/admin/src/main/resources/templates/index.html
index e0a32ac..2de5a7c 100644
--- a/admin/src/main/resources/templates/index.html
+++ b/admin/src/main/resources/templates/index.html
@@ -4,9 +4,88 @@
OfferPie 管理后台
+
+
- OfferPie 管理后台 - 首页
- 管理后台首页占位 - 待实现
+
+
+
+
+

+
+
+ 数据概览
+ 用户管理
+ 订单管理
+ 商品管理
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+