feat: 证书页面添加过滤动画,空状态样式,搜索反馈
This commit is contained in:
parent
3c3c2063b0
commit
8de56bd07c
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@types/sortablejs':
|
||||||
|
specifier: ^1.15.8
|
||||||
|
version: 1.15.8
|
||||||
'@vicons/ionicons5':
|
'@vicons/ionicons5':
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
@ -59,6 +62,9 @@ importers:
|
|||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^4.5.1
|
specifier: ^4.5.1
|
||||||
version: 4.5.1(vue@3.5.18(typescript@5.9.2))
|
version: 4.5.1(vue@3.5.18(typescript@5.9.2))
|
||||||
|
vuedraggable:
|
||||||
|
specifier: ^4.1.0
|
||||||
|
version: 4.1.0(vue@3.5.18(typescript@5.9.2))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/dplayer':
|
'@types/dplayer':
|
||||||
specifier: ^1.25.5
|
specifier: ^1.25.5
|
||||||
@ -589,6 +595,9 @@ packages:
|
|||||||
'@types/node@24.2.1':
|
'@types/node@24.2.1':
|
||||||
resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==}
|
resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==}
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.8':
|
||||||
|
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
|
||||||
|
|
||||||
'@uppy/companion-client@2.2.2':
|
'@uppy/companion-client@2.2.2':
|
||||||
resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==}
|
resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==}
|
||||||
|
|
||||||
@ -1451,6 +1460,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==}
|
resolution: {integrity: sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q==}
|
||||||
engines: {node: '>=12.17.0'}
|
engines: {node: '>=12.17.0'}
|
||||||
|
|
||||||
|
sortablejs@1.14.0:
|
||||||
|
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -1644,6 +1656,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vuedraggable@4.1.0:
|
||||||
|
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.1
|
||||||
|
|
||||||
vueuc@0.4.64:
|
vueuc@0.4.64:
|
||||||
resolution: {integrity: sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==}
|
resolution: {integrity: sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2085,6 +2102,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.10.0
|
undici-types: 7.10.0
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.8': {}
|
||||||
|
|
||||||
'@uppy/companion-client@2.2.2':
|
'@uppy/companion-client@2.2.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@uppy/utils': 4.1.3
|
'@uppy/utils': 4.1.3
|
||||||
@ -3077,6 +3096,8 @@ snapshots:
|
|||||||
|
|
||||||
snabbdom@3.6.2: {}
|
snabbdom@3.6.2: {}
|
||||||
|
|
||||||
|
sortablejs@1.14.0: {}
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
speakingurl@14.0.1: {}
|
speakingurl@14.0.1: {}
|
||||||
@ -3242,6 +3263,11 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.2
|
typescript: 5.9.2
|
||||||
|
|
||||||
|
vuedraggable@4.1.0(vue@3.5.18(typescript@5.9.2)):
|
||||||
|
dependencies:
|
||||||
|
sortablejs: 1.14.0
|
||||||
|
vue: 3.5.18(typescript@5.9.2)
|
||||||
|
|
||||||
vueuc@0.4.64(vue@3.5.18(typescript@5.9.2)):
|
vueuc@0.4.64(vue@3.5.18(typescript@5.9.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.9.2))
|
'@css-render/vue3-ssr': 0.15.14(vue@3.5.18(typescript@5.9.2))
|
||||||
|
@ -54,38 +54,46 @@
|
|||||||
<button class="btn btn-primary" @click="showIssuanceModal = true">颁发证书</button>
|
<button class="btn btn-primary" @click="showIssuanceModal = true">颁发证书</button>
|
||||||
<button class="btn btn-danger">删除</button>
|
<button class="btn btn-danger">删除</button>
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<select class="filter-select">
|
<select v-model="selectedClass" @change="handleFilterChange" class="filter-select">
|
||||||
<option value="">班级名称</option>
|
<option value="">班级名称</option>
|
||||||
<option value="class1">班级名称1</option>
|
<option value="class1">班级名称1</option>
|
||||||
<option value="class2">班级名称2</option>
|
<option value="class2">班级名称2</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input type="text" placeholder="请输入关键词" class="search-input" />
|
<input type="text" placeholder="请输入关键词" v-model="searchKeyword" @input="handleSearchInput"
|
||||||
|
class="search-input" />
|
||||||
<button class="btn btn-search">搜索</button>
|
<button class="btn btn-search">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索无结果空状态 -->
|
||||||
|
<div v-if="issuanceRecords.length === 0 && searchKeyword" class="empty-state search-empty">
|
||||||
|
<h3>未找到相关记录</h3>
|
||||||
|
<p>没有找到包含"{{ searchKeyword }}"的颁发记录,请尝试其他关键词</p>
|
||||||
|
<button class="btn btn-secondary" @click="clearSearch">清除搜索</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无记录空状态 -->
|
||||||
|
<div v-else-if="issuanceRecords.length === 0" class="empty-state">
|
||||||
|
<h3>暂无颁发记录</h3>
|
||||||
|
<p>还没有颁发任何证书,点击"颁发证书"开始颁发</p>
|
||||||
|
<button class="btn btn-primary" @click="showIssuanceModal = true">颁发证书</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 证书颁发记录表格 -->
|
<!-- 证书颁发记录表格 -->
|
||||||
<div class="record-table">
|
<div v-else class="record-table">
|
||||||
<n-data-table
|
<transition-group name="table-fade" tag="div" :key="listKey">
|
||||||
:columns="columns"
|
<n-data-table :key="'table'" :columns="columns" :data="issuanceRecords" :pagination="false"
|
||||||
:data="issuanceRecords"
|
:bordered="false" :single-line="false" :row-key="(row) => row.id" />
|
||||||
:pagination="false"
|
</transition-group>
|
||||||
:bordered="false"
|
|
||||||
:single-line="false"
|
|
||||||
:row-key="(row) => row.id"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 证书颁发模态框 -->
|
<!-- 证书颁发模态框 -->
|
||||||
<CertificateIssuanceModal
|
<CertificateIssuanceModal v-model:show="showIssuanceModal" @confirm="handleIssuanceConfirm" />
|
||||||
v-model:show="showIssuanceModal"
|
|
||||||
@confirm="handleIssuanceConfirm"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -164,6 +172,15 @@ const issuanceRecords = ref<IssuanceRecord[]>([
|
|||||||
// 模态框状态
|
// 模态框状态
|
||||||
const showIssuanceModal = ref(false)
|
const showIssuanceModal = ref(false)
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
|
// 筛选条件
|
||||||
|
const selectedClass = ref('')
|
||||||
|
|
||||||
|
// 强制重新渲染的key
|
||||||
|
const listKey = ref(0)
|
||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -285,6 +302,25 @@ const handleDelete = (record: IssuanceRecord) => {
|
|||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除搜索
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchKeyword.value = ''
|
||||||
|
message.info('已清除搜索条件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索输入
|
||||||
|
const handleSearchInput = () => {
|
||||||
|
// 搜索输入时触发过滤动画
|
||||||
|
listKey.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理筛选变化
|
||||||
|
const handleFilterChange = () => {
|
||||||
|
// 筛选变化时触发过滤动画
|
||||||
|
listKey.value++
|
||||||
|
message.info('筛选条件已更新')
|
||||||
|
}
|
||||||
|
|
||||||
// 处理颁发确认
|
// 处理颁发确认
|
||||||
const handleIssuanceConfirm = (selectedExams: any[]) => {
|
const handleIssuanceConfirm = (selectedExams: any[]) => {
|
||||||
console.log('选中的考试/学习项目:', selectedExams)
|
console.log('选中的考试/学习项目:', selectedExams)
|
||||||
@ -604,8 +640,86 @@ const handleIssuanceConfirm = (selectedExams: any[]) => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========== 过滤动画效果 ========== */
|
||||||
|
|
||||||
|
/* 表格过渡动画 */
|
||||||
|
.table-fade-enter-active,
|
||||||
|
.table-fade-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-fade-move {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 空状态样式 ========== */
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 300px;
|
||||||
|
background: #fff;
|
||||||
|
margin: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state .btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索空状态样式 */
|
||||||
|
.search-empty h3 {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-empty p {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
border-color: #bfbfbf;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -6,7 +6,7 @@
|
|||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
<button class="btn btn-primary" @click="addCertificate">添加证书</button>
|
<button class="btn btn-primary" @click="addCertificate">添加证书</button>
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<select v-model="selectedExam" class="filter-select">
|
<select v-model="selectedExam" @change="handleFilterChange" class="filter-select">
|
||||||
<option value="">考试</option>
|
<option value="">考试</option>
|
||||||
<option value="exam1">期末考试</option>
|
<option value="exam1">期末考试</option>
|
||||||
<option value="exam2">期中考试</option>
|
<option value="exam2">期中考试</option>
|
||||||
@ -14,19 +14,53 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input type="text" placeholder="请输入关键词" v-model="searchKeyword" />
|
<input type="text" placeholder="请输入关键词" v-model="searchKeyword" @input="handleSearchInput" />
|
||||||
<button class="btn btn-search" @click="searchCertificates">搜索</button>
|
<button class="btn btn-search" @click="searchCertificates">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索中状态 -->
|
||||||
|
<div v-if="isSearching" class="search-feedback">
|
||||||
|
<div class="search-spinner"></div>
|
||||||
|
<p>搜索中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选中状态 -->
|
||||||
|
<div v-else-if="isFiltering" class="search-feedback">
|
||||||
|
<div class="search-spinner"></div>
|
||||||
|
<p>筛选中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索无结果空状态 -->
|
||||||
|
<div v-else-if="filteredCertificates.length === 0 && searchKeyword" class="empty-state search-empty">
|
||||||
|
<h3>未找到相关证书</h3>
|
||||||
|
<p>没有找到包含"{{ searchKeyword }}"的证书,请尝试其他关键词</p>
|
||||||
|
<button class="btn btn-secondary" @click="clearSearch">清除搜索</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选无结果空状态 -->
|
||||||
|
<div v-else-if="filteredCertificates.length === 0 && selectedExam" class="empty-state search-empty">
|
||||||
|
<h3>未找到相关证书</h3>
|
||||||
|
<p>没有找到符合筛选条件的证书,请尝试其他筛选条件</p>
|
||||||
|
<button class="btn btn-secondary" @click="clearFilter">清除筛选</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无证书空状态 -->
|
||||||
|
<div v-else-if="filteredCertificates.length === 0" class="empty-state">
|
||||||
|
<h3>暂无证书</h3>
|
||||||
|
<p>还没有创建任何证书,点击"添加证书"开始创建</p>
|
||||||
|
<button class="btn btn-primary" @click="addCertificate">添加证书</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 证书网格展示 -->
|
<!-- 证书网格展示 -->
|
||||||
<div class="certificate-grid">
|
<div v-else class="certificate-grid">
|
||||||
<div v-for="certificate in filteredCertificates" :key="certificate.id" class="certificate-card"
|
<transition-group name="certificate-fade" tag="div" class="certificate-list" :key="listKey">
|
||||||
@click="viewCertificateDetail(certificate)">
|
<div v-for="(certificate, index) in filteredCertificates" :key="`${certificate.id}-${index}`"
|
||||||
|
class="certificate-card" @click="viewCertificateDetail(certificate)">
|
||||||
<div class="certificate-thumbnail">
|
<div class="certificate-thumbnail">
|
||||||
<img :src="certificate.thumbnail" :alt="certificate.name" class="certificate-image" />
|
<img :src="certificate.thumbnail" :alt="certificate.name" class="certificate-image" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="certificate-info">
|
<div class="certificate-info">
|
||||||
<div class="certificate-name">{{ certificate.name }}</div>
|
<div class="certificate-name">{{ certificate.name }}</div>
|
||||||
@ -52,6 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加证书模态框 -->
|
<!-- 添加证书模态框 -->
|
||||||
@ -103,7 +138,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import { useMessage, NModal, NIcon, NAlert } from 'naive-ui'
|
import { useMessage, NModal, NIcon, NAlert } from 'naive-ui'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
@ -115,6 +150,15 @@ const route = useRoute()
|
|||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
const selectedExam = ref('')
|
const selectedExam = ref('')
|
||||||
|
|
||||||
|
// 强制重新渲染的key
|
||||||
|
const listKey = ref(0)
|
||||||
|
|
||||||
|
// 搜索状态
|
||||||
|
const isSearching = ref(false)
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
const isFiltering = ref(false)
|
||||||
|
|
||||||
// 模态框控制
|
// 模态框控制
|
||||||
const showAddModal = ref(false)
|
const showAddModal = ref(false)
|
||||||
const activeFileMenu = ref<number | null>(null)
|
const activeFileMenu = ref<number | null>(null)
|
||||||
@ -213,6 +257,48 @@ const searchCertificates = () => {
|
|||||||
message.info('搜索证书: ' + searchKeyword.value)
|
message.info('搜索证书: ' + searchKeyword.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchKeyword.value = ''
|
||||||
|
message.info('已清除搜索条件')
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFilter = () => {
|
||||||
|
selectedExam.value = ''
|
||||||
|
message.info('已清除筛选条件')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听过滤结果变化,触发动画
|
||||||
|
watch(filteredCertificates, async () => {
|
||||||
|
await nextTick()
|
||||||
|
listKey.value++
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 处理搜索输入
|
||||||
|
const handleSearchInput = async () => {
|
||||||
|
if (!searchKeyword.value.trim()) {
|
||||||
|
isSearching.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching.value = true
|
||||||
|
|
||||||
|
// 模拟搜索延迟,让用户看到搜索动画
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
|
|
||||||
|
isSearching.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理筛选变化
|
||||||
|
const handleFilterChange = async () => {
|
||||||
|
isFiltering.value = true
|
||||||
|
|
||||||
|
// 模拟筛选延迟,让用户看到筛选动画
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
|
|
||||||
|
isFiltering.value = false
|
||||||
|
message.info('筛选条件已更新')
|
||||||
|
}
|
||||||
|
|
||||||
const toggleFileMenu = (id: number) => {
|
const toggleFileMenu = (id: number) => {
|
||||||
console.log('点击了更多操作按钮,ID:', id)
|
console.log('点击了更多操作按钮,ID:', id)
|
||||||
activeFileMenu.value = activeFileMenu.value === id ? null : id
|
activeFileMenu.value = activeFileMenu.value === id ? null : id
|
||||||
@ -405,6 +491,10 @@ document.addEventListener('click', closeFileMenu)
|
|||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.certificate-list {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
/* 证书卡片 */
|
/* 证书卡片 */
|
||||||
.certificate-card {
|
.certificate-card {
|
||||||
padding: 50px 20px 10px 20px;
|
padding: 50px 20px 10px 20px;
|
||||||
@ -676,6 +766,7 @@ document.addEventListener('click', closeFileMenu)
|
|||||||
border-color: #40a9ff;
|
border-color: #40a9ff;
|
||||||
color: #40a9ff;
|
color: #40a9ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media (max-width: 1400px) {
|
@media (max-width: 1400px) {
|
||||||
.certificate-grid {
|
.certificate-grid {
|
||||||
@ -747,4 +838,127 @@ document.addEventListener('click', closeFileMenu)
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========== 过滤动画效果 ========== */
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
.certificate-fade-enter-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-fade-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-fade-move {
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保动画可见 */
|
||||||
|
.certificate-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 搜索反馈样式 ========== */
|
||||||
|
|
||||||
|
.search-feedback {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 200px;
|
||||||
|
background: #fff;
|
||||||
|
margin: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-spinner {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #0288D1;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-feedback p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 空状态样式 ========== */
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 400px;
|
||||||
|
background: #fff;
|
||||||
|
margin: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0 0 24px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state .btn {
|
||||||
|
padding: 10px 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索空状态样式 */
|
||||||
|
.search-empty h3 {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-empty p {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user