first commit

This commit is contained in:
2025-11-24 16:06:44 +08:00
commit e57d497751
165 changed files with 59349 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
<template>
<ListPageLayout title="数据大厅" subtitle="发现并订阅您需要的数据产品">
<template #filters>
<FilterSection>
<FilterItem label="产品分类">
<el-select v-model="filters.category_id" placeholder="选择分类" clearable @change="handleFilterChange"
class="w-full">
<el-option v-for="category in categories" :key="category.id" :label="category.name" :value="category.id" />
</el-select>
</FilterItem>
<FilterItem label="产品类型">
<el-select v-model="filters.is_package" placeholder="选择类型" clearable @change="handleFilterChange"
class="w-full">
<el-option label="单品" value="false" />
<el-option label="组合包" value="true" />
</el-select>
</FilterItem>
<FilterItem label="订阅状态">
<el-select v-model="filters.is_subscribed" placeholder="选择订阅状态" clearable @change="handleFilterChange"
class="w-full">
<el-option label="已订阅" value="true" />
<el-option label="未订阅" value="false" />
</el-select>
</FilterItem>
<FilterItem label="搜索产品">
<el-input v-model="filters.keyword" placeholder="输入产品名称或编号" clearable @input="handleSearch" class="w-full" />
</FilterItem>
<template #stats>
共找到 {{ total }} 个产品
</template>
<template #buttons>
<el-button @click="resetFilters">重置筛选</el-button>
<el-button type="primary" @click="loadProducts">应用筛选</el-button>
</template>
</FilterSection>
</template>
<template #table>
<div v-if="loading" class="flex justify-center items-center py-12">
<el-loading size="large" />
</div>
<div v-else-if="products.length === 0" class="text-center py-12">
<el-empty description="暂无产品数据" />
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-4">
<ProductCard v-for="product in products" :key="product.id" :product="product"
:is-subscribed="product.is_subscribed" @view-detail="handleViewDetail"
@subscribe="handleSubscribe" />
</div>
</template>
<template #pagination>
<el-pagination v-if="total > 0" v-model:current-page="currentPage" v-model:page-size="pageSize"
:page-sizes="[12, 24, 48, 96]" :total="total" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</template>
</ListPageLayout>
</template>
<script setup>
import { categoryApi, productApi } from '@/api'
import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
import ProductCard from '@/components/product/ProductCard.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
// 响应式数据
const loading = ref(false)
const products = ref([])
const categories = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(12)
// 筛选条件
const filters = reactive({
category_id: '',
is_package: null,
is_subscribed: null,
keyword: ''
})
// 搜索防抖
let searchTimer = null
// 初始化
onMounted(() => {
loadCategories()
loadProducts()
})
// 加载产品分类
const loadCategories = async () => {
try {
const response = await categoryApi.getCategories({ page: 1, page_size: 100 })
categories.value = response.data?.items || []
} catch (error) {
console.error('加载分类失败:', error)
}
}
// 加载产品列表
const loadProducts = async () => {
loading.value = true
try {
const params = {
page: currentPage.value,
page_size: pageSize.value,
...filters
}
const response = await productApi.getProducts(params)
products.value = response.data?.items || []
total.value = response.data?.total || 0
} catch (error) {
console.error('加载产品失败:', error)
ElMessage.error('加载产品失败')
} finally {
loading.value = false
}
}
// 处理筛选变化
const handleFilterChange = () => {
currentPage.value = 1
loadProducts()
}
// 处理搜索
const handleSearch = () => {
if (searchTimer) {
clearTimeout(searchTimer)
}
searchTimer = setTimeout(() => {
currentPage.value = 1
loadProducts()
}, 500)
}
// 重置筛选
const resetFilters = () => {
filters.category_id = ''
filters.is_package = null
filters.is_subscribed = null
filters.keyword = ''
currentPage.value = 1
loadProducts()
}
// 处理分页大小变化
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
loadProducts()
}
// 处理当前页变化
const handleCurrentChange = (page) => {
currentPage.value = page
loadProducts()
}
// 查看产品详情
const handleViewDetail = (product) => {
router.push(`/products/${product.id}`)
}
// 订阅产品
const handleSubscribe = async (product) => {
try {
await ElMessageBox.confirm(
`确定要订阅产品"${product.name}"吗?`,
'确认订阅',
{
confirmButtonText: '确定订阅',
cancelButtonText: '取消',
type: 'info'
}
)
await productApi.subscribeProduct(product.id)
ElMessage.success('订阅成功')
// 重新加载产品列表以更新订阅状态
await loadProducts()
} catch (error) {
if (error !== 'cancel') {
console.error('订阅失败:', error)
ElMessage.error('订阅失败')
}
}
}
</script>
<style scoped>
/* 页面特定样式可以在这里添加 */
</style>