|
|
@@ -1,41 +1,144 @@
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref } from 'vue'
|
|
|
+import { ref, onMounted, computed } from 'vue'
|
|
|
+import { Download, Search } from '@element-plus/icons-vue'
|
|
|
+import WindowsIcon from '/src/assets/icons/windows.svg'
|
|
|
+import LinuxIcon from '/src/assets/icons/linux.svg'
|
|
|
+import AndroidIcon from '/src/assets/icons/android.svg'
|
|
|
+import SdkIcon from '/src/assets/icons/sdk.svg'
|
|
|
+import DocumentIcon from '/src/assets/icons/document.svg'
|
|
|
+import ExcelIcon from '/src/assets/icons/excel.svg'
|
|
|
|
|
|
// 下载分类
|
|
|
const categories = ref([
|
|
|
- { id: 1, name: 'Windows' },
|
|
|
- { id: 50, name: '信创/Linux' },
|
|
|
- { id: 56, name: 'CMSV7客户端' },
|
|
|
- { id: 60, name: 'APP' },
|
|
|
- { id: 28, name: '对接SDK' },
|
|
|
- { id: 37, name: '说明书' },
|
|
|
- { id: 43, name: '功能列表' },
|
|
|
- { id: 3, name: '协议标准' }
|
|
|
+ { id: 0, name: '全部' },
|
|
|
+ { id: 1, name: 'Windows', icon: WindowsIcon },
|
|
|
+ { id: 2, name: '信创/Linux', icon: LinuxIcon },
|
|
|
+ { id: 3, name: '安卓APP', icon: AndroidIcon },
|
|
|
+ { id: 4, name: '对接SDK', icon: SdkIcon },
|
|
|
+ { id: 5, name: '说明书', icon: DocumentIcon },
|
|
|
+ { id: 6, name: '功能列表', icon: ExcelIcon },
|
|
|
])
|
|
|
|
|
|
+// 下载项类型
|
|
|
+interface DownloadItem {
|
|
|
+ id: number
|
|
|
+ title: string
|
|
|
+ description: string
|
|
|
+ version: string
|
|
|
+ file: string
|
|
|
+ category: number | number[] // 类别可以是单个数字或数组,用于支持多个分类
|
|
|
+ size?: string // 可选属性,动态添加
|
|
|
+}
|
|
|
+
|
|
|
// 下载项数据
|
|
|
-const downloads = ref([
|
|
|
+const downloads = ref<DownloadItem[]>([
|
|
|
{
|
|
|
id: 1,
|
|
|
- title: 'CMSserverV6服务器',
|
|
|
- description: '服务器安装包,集公务车、渣土车、第三方、网约车、主动安全、校车六位一体的版本。',
|
|
|
- version: '7.34.0.5_20250102',
|
|
|
- size: '957.65 MB',
|
|
|
- file: '/downloads/CMSServerV6-WIN-7.34.0.5-20250102.zip'
|
|
|
+ title: 'IPVSP-IPU-2.0',
|
|
|
+ description: 'IP视频服务平台安装包,支持多种视频协议和格式。',
|
|
|
+ version: '2.0.0',
|
|
|
+ file: '/downloads/IPVSP-IPU-2.0.apk',
|
|
|
+ category: 3 // 安卓APP
|
|
|
},
|
|
|
{
|
|
|
id: 2,
|
|
|
- title: 'CMSV6客户端(64位)',
|
|
|
- description: '64位电脑客户端,车载平台和执法仪平台通用,适用于64位的Windows操作系统。',
|
|
|
- version: '7.34.0.5_20250102',
|
|
|
- size: '108.19 MB',
|
|
|
- file: '/downloads/CMSV6-WIN-7.34.0.5-20250102.zip'
|
|
|
+ title: 'IPVSP安装包操作说明',
|
|
|
+ description: '详细说明IPVSP-IPU-2.0的安装和使用方法。',
|
|
|
+ version: '1.0.0',
|
|
|
+ file: '/downloads/IPVSP安装包操作说明.docx',
|
|
|
+ category: 5 // 说明书
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ title: 'IPVSP远程专家协通系统技术说明书',
|
|
|
+ description: '详细列出IPVSP-IPU-2.0的所有功能和特性。',
|
|
|
+ version: '23.3.0',
|
|
|
+ file: '/downloads/IPVSP远程专家协同系统技术说明书23_3_0.docx',
|
|
|
+ category: [ 5, 7 ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ title: 'AnLinkSetup',
|
|
|
+ description: 'AnLinkSetup安装包,用于安卓设备与PC的连接,支持WiFi和USB连接,多点触控,共享剪切板等。',
|
|
|
+ version: '1.0.0',
|
|
|
+ file: '/downloads/AnLinkSetup.zip',
|
|
|
+ category: 1 // Windows
|
|
|
}
|
|
|
])
|
|
|
|
|
|
+
|
|
|
+// 获取文件大小(MB)
|
|
|
+const getFileSize = async (filePath: string) => {
|
|
|
+ try {
|
|
|
+ const response = await fetch(filePath)
|
|
|
+ const contentLength = response.headers.get('content-length')
|
|
|
+ if (contentLength) {
|
|
|
+ const sizeMB = (parseInt(contentLength) / (1024 * 1024)).toFixed(2)
|
|
|
+ return `${sizeMB} MB`
|
|
|
+ }
|
|
|
+ return '未知大小'
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取文件大小失败:', error)
|
|
|
+ return '未知大小'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化下载项数据
|
|
|
+const initDownloads = async () => {
|
|
|
+ for (const item of downloads.value) {
|
|
|
+ const size = await getFileSize(item.file)
|
|
|
+ // 使用类型断言确保类型安全
|
|
|
+ ;(item as DownloadItem).size = size
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ initDownloads()
|
|
|
+})
|
|
|
+
|
|
|
+// 搜索关键词
|
|
|
+const searchKeyword = ref('')
|
|
|
// 当前选中分类
|
|
|
-const activeCategory = ref(-1)
|
|
|
+const activeCategory = ref(0) // 默认选中"全部"
|
|
|
+
|
|
|
+// 处理搜索
|
|
|
+const handleSearch = () => {
|
|
|
+ // 搜索时保持当前分类状态
|
|
|
+}
|
|
|
+
|
|
|
+// 计算筛选后的下载项
|
|
|
+const filteredDownloads = computed<DownloadItem[]>(() => {
|
|
|
+ let result = downloads.value
|
|
|
+
|
|
|
+ // 分类筛选
|
|
|
+ if (activeCategory.value !== 0) {
|
|
|
+ result = result.filter(item =>
|
|
|
+ Array.isArray(item.category)
|
|
|
+ ? item.category.includes(activeCategory.value)
|
|
|
+ : item.category === activeCategory.value
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // 模糊搜索
|
|
|
+ if (searchKeyword.value.trim()) {
|
|
|
+ const keyword = searchKeyword.value.toLowerCase()
|
|
|
+ result = result.filter(item =>
|
|
|
+ item.title.toLowerCase().includes(keyword) ||
|
|
|
+ item.description.toLowerCase().includes(keyword)
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+})
|
|
|
+
|
|
|
+// 获取分类图标
|
|
|
+const getCategoryIcon = (category: number | number[]) => {
|
|
|
+ if (Array.isArray(category)) {
|
|
|
+ return categories.value.find(c => c.id === category[0])?.icon;
|
|
|
+ }
|
|
|
+ return categories.value.find(c => c.id === category)?.icon;
|
|
|
+}
|
|
|
|
|
|
// 处理下载
|
|
|
const handleDownload = (filePath: string) => {
|
|
|
@@ -52,82 +155,114 @@ const handleDownload = (filePath: string) => {
|
|
|
<div class="download-center">
|
|
|
<!-- 搜索区域 -->
|
|
|
<div class="search-box">
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- placeholder="请输入名称或内容"
|
|
|
- class="search-input"
|
|
|
- >
|
|
|
- <button class="search-button">搜索</button>
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ :prefix-icon="Search"
|
|
|
+ placeholder="请输入名称或描述内容搜索"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
<!-- 分类筛选 -->
|
|
|
<div class="category-filter">
|
|
|
- <div
|
|
|
+ <el-button
|
|
|
v-for="category in categories"
|
|
|
:key="category.id"
|
|
|
- class="category-item"
|
|
|
- :class="{ active: activeCategory === category.id }"
|
|
|
+ :type="activeCategory === category.id ? 'primary' : ''"
|
|
|
@click="activeCategory = category.id"
|
|
|
+ size="small"
|
|
|
>
|
|
|
+ <template v-if="category.icon">
|
|
|
+ <img :src="category.icon" class="category-icon" alt=""/>
|
|
|
+ </template>
|
|
|
{{ category.name }}
|
|
|
- </div>
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
|
|
|
<!-- 下载列表 -->
|
|
|
<div class="download-list">
|
|
|
- <div
|
|
|
- v-for="item in downloads"
|
|
|
+ <el-card
|
|
|
+ v-for="item in filteredDownloads"
|
|
|
:key="item.id"
|
|
|
- class="download-item"
|
|
|
+ class="card"
|
|
|
+ shadow="hover"
|
|
|
>
|
|
|
- <div class="download-info">
|
|
|
- <h3>{{ item.title }}</h3>
|
|
|
- <p>{{ item.description }}</p>
|
|
|
- <div class="download-meta">
|
|
|
- <span>版本: {{ item.version }}</span>
|
|
|
- <span>大小: {{ item.size }}</span>
|
|
|
+ <div class="card-bg-icon">
|
|
|
+ <template v-if="getCategoryIcon(item.category)">
|
|
|
+ <img
|
|
|
+ :src="getCategoryIcon(item.category)"
|
|
|
+ class="bg-icon-img"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <path d="M50 0 L100 50 L50 100 L0 50 Z" fill="rgba(255,255,255,0.1)"/>
|
|
|
+ </svg>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <div class="card-header">
|
|
|
+ <h3>{{ item.title }}</h3>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <p class="card-description">{{ item.description }}</p>
|
|
|
+ <div class="card-footer">
|
|
|
+ <div class="download-meta">
|
|
|
+ <span>版本: {{ item.version }}</span>
|
|
|
+ <span v-if="item.size">大小: {{ item.size }}</span>
|
|
|
+ <span v-else>正在获取文件大小...</span>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ :icon="Download"
|
|
|
+ @click="handleDownload(item.file)"
|
|
|
+ size="small"
|
|
|
+ style="margin-left: auto;"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <button
|
|
|
- class="download-button"
|
|
|
- @click="handleDownload(item.file)"
|
|
|
- >
|
|
|
- 下载
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ </el-card>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
-<style scoped>
|
|
|
+<style lang="scss" scoped>
|
|
|
.download-center {
|
|
|
max-width: 1200px;
|
|
|
margin: 0 auto;
|
|
|
padding: 2rem;
|
|
|
}
|
|
|
|
|
|
-.search-box {
|
|
|
- display: flex;
|
|
|
- margin-bottom: 2rem;
|
|
|
- gap: 1rem;
|
|
|
+.category-icon {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ margin-right: 6px;
|
|
|
+ vertical-align: middle;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
-.search-input {
|
|
|
- flex: 1;
|
|
|
- padding: 0.75rem 1rem;
|
|
|
- border: 1px solid #ddd;
|
|
|
- border-radius: 4px;
|
|
|
- font-size: 1rem;
|
|
|
+.bg-icon-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ opacity: 0.5;
|
|
|
+ object-fit: contain;
|
|
|
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
}
|
|
|
|
|
|
-.search-button {
|
|
|
- padding: 0 2rem;
|
|
|
- background: #3270FF;
|
|
|
- color: white;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 1rem;
|
|
|
+.card:hover .bg-icon-img {
|
|
|
+ transform: scale(1.2) rotate(5deg);
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+.search-box {
|
|
|
+ margin-bottom: 2rem;
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ height: 48px;
|
|
|
+ padding: 0 16px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.category-filter {
|
|
|
@@ -137,57 +272,72 @@ const handleDownload = (filePath: string) => {
|
|
|
margin-bottom: 2rem;
|
|
|
}
|
|
|
|
|
|
-.category-item {
|
|
|
- padding: 0.5rem 1rem;
|
|
|
- background: #f5f5f5;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
-}
|
|
|
-
|
|
|
-.category-item.active {
|
|
|
- background: #3270FF;
|
|
|
- color: white;
|
|
|
-}
|
|
|
-
|
|
|
.download-list {
|
|
|
display: grid;
|
|
|
gap: 1.5rem;
|
|
|
-}
|
|
|
|
|
|
-.download-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 1.5rem;
|
|
|
- background: white;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
-}
|
|
|
+ .card {
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ position: relative;
|
|
|
+ background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(247,250,255,0.9) 100%);
|
|
|
+ overflow: hidden;
|
|
|
|
|
|
-.download-info h3 {
|
|
|
- margin: 0 0 0.5rem;
|
|
|
- font-size: 1.25rem;
|
|
|
-}
|
|
|
+ :deep(.el-card__body) {
|
|
|
+ padding: 1.5rem;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
|
|
|
-.download-info p {
|
|
|
- margin: 0 0 1rem;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
+ .card-bg-icon {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ opacity: 0.3;
|
|
|
+ z-index: 0;
|
|
|
+ }
|
|
|
|
|
|
-.download-meta {
|
|
|
- display: flex;
|
|
|
- gap: 1rem;
|
|
|
- color: #888;
|
|
|
- font-size: 0.875rem;
|
|
|
-}
|
|
|
+ .card-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
|
|
|
-.download-button {
|
|
|
- padding: 0.5rem 1.5rem;
|
|
|
- background: #3270FF;
|
|
|
- color: white;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 1rem;
|
|
|
+ h3 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 1.25rem;
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-description {
|
|
|
+ margin: 0;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ font-size: 0.95rem;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .download-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 1rem;
|
|
|
+ font-size: 1rem;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ > :last-child {
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
</style>
|