diff --git a/src/App.tsx b/src/App.tsx index be2a2e9..0f17538 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import UsersPage from './components/pages/UsersPage' import EquipmentPage from './components/pages/EquipmentPage' import StatisticsPage from './components/pages/StatisticsPage' import MallsPage from './components/pages/MallsPage' +import VersionPage from './components/pages/VersionPage' import DynamicPage from './components/DynamicPage' function App() { @@ -25,6 +26,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/pages/MerchantsPage.tsx b/src/components/pages/MerchantsPage.tsx index 338cfea..8d0a854 100644 --- a/src/components/pages/MerchantsPage.tsx +++ b/src/components/pages/MerchantsPage.tsx @@ -15,7 +15,7 @@ import { DialogTitle, DialogTrigger, } from "../ui/dialog" -import { Plus, Search, Filter, Download, Calendar, AlertTriangle, Store, Eye, Building2, MapPin } from "lucide-react" +import { Plus, Search, Filter, Download, Calendar, AlertTriangle, Store, Eye, Building2, MapPin, ChevronLeft, ChevronRight } from "lucide-react" import { apiGet, apiPost, apiPut } from "../../lib/services/api" // 总商户数据类型 @@ -77,6 +77,8 @@ export default function MerchantsPage() { const [merchantEquipments, setMerchantEquipments] = useState<{[key: string]: any[]}>({}) const [loadingMerchantEquipments, setLoadingMerchantEquipments] = useState<{[key: string]: boolean}>({}) const [userRole, setUserRole] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) const [newMerchant, setNewMerchant] = useState({ name: "", @@ -213,10 +215,10 @@ export default function MerchantsPage() { } // 获取商户列表 - const fetchMerchants = async () => { + const fetchMerchants = async (page = currentPage, size = pageSize) => { setLoadingMerchants(true) try { - const response = await apiGet('/back/merchants/list') + const response = await apiGet(`/back/merchants/list?pageNum=${page}&pageSize=${size}`) if (response.code === 200) { const merchantsData = response.rows || [] setTotal(parseInt(response.total) || 0) @@ -245,6 +247,19 @@ export default function MerchantsPage() { } } + // 页码变化处理 + const handlePageChange = (page: number) => { + setCurrentPage(page) + fetchMerchants(page, pageSize) + } + + // 每页大小变化处理 + const handlePageSizeChange = (size: number) => { + setPageSize(size) + setCurrentPage(1) + fetchMerchants(1, size) + } + // 组件加载时获取数据 useEffect(() => { fetchUserRole() @@ -323,7 +338,7 @@ export default function MerchantsPage() { setIsAddMerchantOpen(false) // 刷新商户列表 - fetchMerchants() + fetchMerchants(currentPage, pageSize) } else { console.error('添加商户失败:', result) alert('添加商户失败:' + (result.msg || '未知错误')) @@ -460,7 +475,7 @@ export default function MerchantsPage() { console.log('编辑商户成功:', result) alert('编辑商户成功!') setIsEditMerchantOpen(false) - fetchMerchants() + fetchMerchants(currentPage, pageSize) } else { console.error('编辑商户失败:', result) alert('编辑商户失败:' + (result.msg || '未知错误')) @@ -576,7 +591,7 @@ export default function MerchantsPage() { 商户列表 - 查看和管理所有商户信息及设备状态 + 查看和管理所有商户信息及设备状态 - 共 {total} 条记录,当前第 {currentPage} 页
@@ -705,6 +720,56 @@ export default function MerchantsPage() {
+ + {/* Pagination */} +
+
+

每页显示

+ +

+ 共 {total} 条记录 +

+
+ +
+

+ 第 {currentPage} 页,共 {Math.ceil(total / pageSize)} 页 +

+
+ + +
+
+
diff --git a/src/components/pages/VersionPage.tsx b/src/components/pages/VersionPage.tsx new file mode 100644 index 0000000..0c2339f --- /dev/null +++ b/src/components/pages/VersionPage.tsx @@ -0,0 +1,803 @@ +import React, { useState, useEffect } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card" +import { Button } from "../ui/button" +import { Input } from "../ui/input" +import { Badge } from "../ui/badge" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog" +import { Label } from "../ui/label" +import { Textarea } from "../ui/textarea" +import { + Plus, + Search, + Download, + Edit, + Trash2, + Upload, + RefreshCw, + ChevronLeft, + ChevronRight, +} from "lucide-react" +import { apiGet, apiPost, apiPut, apiDelete } from "../../lib/services/api" +import { getUserToken } from "../../lib/utils/storage" + +// 版本信息数据类型接口 +interface Version { + id: string + versionCode: number + versionName: string + isForce: string + channel: string + downloadUrl: string + updateLog: string + size: string + createTime: string + updateTime: string +} + +// 渠道中文名称映射 +const channelNameMap: Record = { + 'official': '官方', + 'xiaomi': '小米', + 'huawei': '华为', + 'oppo': 'OPPO', + 'vivo': 'VIVO', + 'meizu': '魅族', + 'honor': '荣耀', + 'yingyongbao': '应用宝', + 'ios': 'iOS' +} + +export default function VersionPage() { + const [versions, setVersions] = useState([]) + const [channels, setChannels] = useState([]) + const [searchTerm, setSearchTerm] = useState("") + const [isForceFilter, setIsForceFilter] = useState("all") + const [channelFilter, setChannelFilter] = useState("all") + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) + const [loading, setLoading] = useState(false) + const [submitting, setSubmitting] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [total, setTotal] = useState(0) + const [selectedFile, setSelectedFile] = useState(null) + const [uploadProgress, setUploadProgress] = useState(0) + + const [formData, setFormData] = useState({ + versionName: "", + versionCode: 4, + isForce: "0", + channel: "", + downloadUrl: "", + updateLog: "", + size: "", + }) + + const [editData, setEditData] = useState(null) + + // 获取版本列表 + const fetchVersions = async (page = currentPage, size = pageSize) => { + setLoading(true) + try { + const params: any = { + pageNum: page, + pageSize: size, + } + + if (searchTerm) params.versionName = searchTerm + if (isForceFilter !== "all") params.isForce = isForceFilter + if (channelFilter !== "all") params.channel = channelFilter + + const response = await apiGet('/back/version/list', params) + if (response.code === 200) { + setVersions(response.rows || []) + setTotal(parseInt(response.total) || 0) + } + } catch (error) { + console.error('获取版本列表失败:', error) + } finally { + setLoading(false) + } + } + + // 获取渠道列表 + const fetchChannels = async () => { + try { + const response = await apiGet('/back/version/getChannel') + if (response.code === 200) { + setChannels(response.data || []) + } + } catch (error) { + console.error('获取渠道列表失败:', error) + } + } + + // 组件加载时获取数据 + useEffect(() => { + fetchVersions() + fetchChannels() + }, []) + + // 分页变化时重新获取数据 + useEffect(() => { + fetchVersions(currentPage, pageSize) + }, [currentPage, pageSize]) + + // 处理文件选择 + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) { + console.log('没有选择文件') + return + } + + console.log('选择的文件:', file.name, '大小:', file.size) + + // 检查文件大小(500MB限制) + const maxSize = 500 * 1024 * 1024 + if (file.size > maxSize) { + alert('上传文件大小不能超过 500MB!') + return + } + + setSelectedFile(file) + + // 计算文件大小 + const fileSize = file.size + let sizeText = '' + if (fileSize < 1024) { + sizeText = fileSize + ' B' + } else if (fileSize < 1024 * 1024) { + sizeText = (fileSize / 1024).toFixed(2) + ' KB' + } else if (fileSize < 1024 * 1024 * 1024) { + sizeText = (fileSize / (1024 * 1024)).toFixed(2) + ' MB' + } else { + sizeText = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + ' GB' + } + + console.log('文件大小:', sizeText) + + if (isEditDialogOpen && editData) { + setEditData({ ...editData, size: sizeText }) + } else { + setFormData({ ...formData, size: sizeText }) + } + + // 上传文件 + console.log('开始上传文件...') + await uploadFile(file) + } + + // 上传文件到服务器 + const uploadFile = async (file: File) => { + console.log('uploadFile 函数被调用') + const formDataUpload = new FormData() + formDataUpload.append('file', file) + + console.log('准备上传的文件:', file.name) + + try { + const token = getUserToken() + console.log('Token:', token ? '存在' : '不存在') + + // 使用当前页面的域名和端口,自动适配开发和生产环境 + const baseUrl = window.location.origin.includes('localhost') + ? 'http://localhost:8080/api' + : '/api' + const url = `${baseUrl}/back/version/upload` + console.log('上传URL:', url) + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formDataUpload + }) + + console.log('响应状态:', response.status) + const result = await response.json() + console.log('响应结果:', result) + + if (result.code === 200) { + if (isEditDialogOpen && editData) { + setEditData({ ...editData, downloadUrl: result.data }) + } else { + setFormData({ ...formData, downloadUrl: result.data }) + } + alert('文件上传成功') + } else { + alert('文件上传失败:' + result.msg) + } + } catch (error) { + console.error('文件上传失败:', error) + alert('文件上传失败: ' + (error instanceof Error ? error.message : '未知错误')) + } + } + + // 添加版本 + const handleAddVersion = async () => { + if (!formData.versionName || !formData.versionCode || !formData.channel || !formData.downloadUrl) { + alert('请填写所有必填字段') + return + } + + setSubmitting(true) + try { + const response = await apiPost('/back/version', { + ...formData, + versionCode: Number(formData.versionCode) + }) + + if (response.code === 200) { + alert('新增成功') + setIsAddDialogOpen(false) + resetForm() + fetchVersions(currentPage, pageSize) + } else { + alert('新增失败:' + response.msg) + } + } catch (error) { + console.error('新增版本失败:', error) + alert('新增失败,请稍后重试') + } finally { + setSubmitting(false) + } + } + + // 打开编辑对话框 + const handleEditVersion = async (version: Version) => { + setEditData(version) + setIsEditDialogOpen(true) + } + + // 更新版本 + const handleUpdateVersion = async () => { + if (!editData) return + + if (!editData.versionName || !editData.versionCode || !editData.channel || !editData.downloadUrl) { + alert('请填写所有必填字段') + return + } + + setSubmitting(true) + try { + const response = await apiPut('/back/version', { + ...editData, + versionCode: Number(editData.versionCode) + }) + + if (response.code === 200) { + alert('修改成功') + setIsEditDialogOpen(false) + setEditData(null) + fetchVersions(currentPage, pageSize) + } else { + alert('修改失败:' + response.msg) + } + } catch (error) { + console.error('修改版本失败:', error) + alert('修改失败,请稍后重试') + } finally { + setSubmitting(false) + } + } + + // 删除版本 + const handleDeleteVersion = async (id: string) => { + if (!window.confirm('是否确认删除该版本信息?')) return + + try { + const response = await apiDelete(`/back/version/${id}`) + if (response.code === 200) { + alert('删除成功') + fetchVersions(currentPage, pageSize) + } else { + alert('删除失败:' + response.msg) + } + } catch (error) { + console.error('删除版本失败:', error) + alert('删除失败,请稍后重试') + } + } + + // 重置表单 + const resetForm = () => { + setFormData({ + versionName: "", + versionCode: 4, + isForce: "0", + channel: "", + downloadUrl: "", + updateLog: "", + size: "", + }) + setSelectedFile(null) + } + + // 搜索 + const handleSearch = () => { + setCurrentPage(1) + fetchVersions(1, pageSize) + } + + // 重置搜索 + const handleReset = () => { + setSearchTerm("") + setIsForceFilter("all") + setChannelFilter("all") + setCurrentPage(1) + fetchVersions(1, pageSize) + } + + // 导出 + const handleExport = () => { + // 实现导出功能 + alert('导出功能开发中') + } + + // 分页处理 + const handlePageChange = (page: number) => { + setCurrentPage(page) + } + + const handlePageSizeChange = (size: number) => { + setPageSize(size) + setCurrentPage(1) + } + + return ( +
+ {/* 页面标题 */} +
+

版本管理

+

管理应用版本更新信息

+
+ + {/* 搜索表单 */} + + +
+
+ + setSearchTerm(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSearch()} + /> +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + {/* 操作按钮 */} +
+ + +
+ + {/* 版本列表 */} + + + 版本列表 + + +
+ + + + 主键ID + 版本代码 + 版本名称 + 强制更新 + 渠道标识 + APK下载 + 更新日志 + 安装包 + 操作 + + + + {loading ? ( + + +
加载中...
+
+
+ ) : versions.length === 0 ? ( + + +
暂无数据
+
+
+ ) : ( + versions.map((version) => ( + + {version.id} + {version.versionCode} + {version.versionName} + + + {version.isForce === '1' ? '是' : '否'} + + + + + {channelNameMap[version.channel] || version.channel} + + + +
+ {version.downloadUrl} +
+
+ +
+ {version.updateLog || '-'} +
+
+ {version.size} + +
+ + +
+
+
+ )) + )} +
+
+
+ + {/* 分页 */} +
+
+

每页显示

+ +

+ 共 {total} 条记录 +

+
+ +
+

+ 第 {currentPage} 页,共 {Math.ceil(total / pageSize)} 页 +

+
+ + +
+
+
+
+
+ + {/* 新增对话框 */} + + + + 添加应用版本更新信息 + 填写版本信息并上传APK文件 + +
+
+ + setFormData({ ...formData, versionName: e.target.value })} + placeholder="请输入版本名称" + /> +
+
+ + setFormData({ ...formData, versionCode: parseInt(e.target.value) || 4 })} + placeholder="请输入版本代码" + /> +
+
+ + +
+
+ + +
+
+ +
+ + + 文件大小不超过500MB +
+ +
+
+ +