更新 Vite 配置中的 API 目标地址为本地地址,新增按状态批量删除链接的功能,包含相关对话框和表单验证逻辑,优化移动端样式以提升用户体验。
This commit is contained in:
138
docs/按狀態批量刪除鏈接前端實現說明.md
Normal file
138
docs/按狀態批量刪除鏈接前端實現說明.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# 按狀態批量刪除鏈接前端實現說明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
在現有的鏈接生成頁面中新增了按狀態批量刪除鏈接的功能,用戶可以根據指定的狀態批量刪除自己的鏈接。
|
||||||
|
|
||||||
|
## 實現內容
|
||||||
|
|
||||||
|
### 1. API 接口實現
|
||||||
|
**文件:** `src/api/links.js`
|
||||||
|
|
||||||
|
新增了 `batchDeleteByStatus` 方法:
|
||||||
|
```javascript
|
||||||
|
// 按状态批量删除链接
|
||||||
|
export function batchDeleteByStatus(payload) {
|
||||||
|
// payload: { statusList: string[], confirmDelete: boolean }
|
||||||
|
return http.post('/api/link/batch-delete-by-status', payload)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. UI 界面實現
|
||||||
|
**文件:** `src/views/links/LinkGenerate.vue`
|
||||||
|
|
||||||
|
#### 2.1 操作入口
|
||||||
|
- **桌面端:** 在鏈接列表卡片頭部新增「按狀態刪除」按鈕
|
||||||
|
- **移動端:** 在移動端操作按鈕組中新增相同功能按鈕
|
||||||
|
|
||||||
|
#### 2.2 對話框界面
|
||||||
|
- 使用 `el-dialog` 組件創建批量刪除對話框
|
||||||
|
- 包含狀態選擇(多選框組)和確認刪除選項
|
||||||
|
- 支持響應式設計,移動端優化顯示
|
||||||
|
|
||||||
|
#### 2.3 狀態選項
|
||||||
|
支持的鏈接狀態:
|
||||||
|
- **NEW**: 新建
|
||||||
|
- **USING**: 使用中
|
||||||
|
- **LOGGED_IN**: 已登錄
|
||||||
|
- **COMPLETED**: 已完成
|
||||||
|
- **REFUNDED**: 已退款
|
||||||
|
- **EXPIRED**: 已過期
|
||||||
|
|
||||||
|
### 3. 業務邏輯實現
|
||||||
|
|
||||||
|
#### 3.1 表單驗證
|
||||||
|
- 狀態列表不能為空
|
||||||
|
- 必須勾選確認刪除選項
|
||||||
|
- 使用 Element Plus 的表單驗證規則
|
||||||
|
|
||||||
|
#### 3.2 安全確認機制
|
||||||
|
- 表單提交前進行驗證
|
||||||
|
- 二次確認對話框顯示要刪除的狀態
|
||||||
|
- 防止誤操作
|
||||||
|
|
||||||
|
#### 3.3 結果處理
|
||||||
|
- 顯示刪除結果統計(成功/失敗數量)
|
||||||
|
- 失敗時顯示詳細錯誤信息
|
||||||
|
- 自動刷新鏈接列表
|
||||||
|
|
||||||
|
### 4. 響應式數據結構
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const batchDeleteByStatusDialog = reactive({
|
||||||
|
visible: false, // 對話框顯示狀態
|
||||||
|
loading: false, // 刪除操作加載狀態
|
||||||
|
form: {
|
||||||
|
statusList: [], // 選中的狀態列表
|
||||||
|
confirmDelete: false // 確認刪除選項
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// 表單驗證規則
|
||||||
|
statusList: [...],
|
||||||
|
confirmDelete: [...]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 主要方法
|
||||||
|
|
||||||
|
#### 5.1 `showBatchDeleteByStatusDialog()`
|
||||||
|
- 顯示批量刪除對話框
|
||||||
|
- 重置表單狀態
|
||||||
|
|
||||||
|
#### 5.2 `closeBatchDeleteByStatusDialog()`
|
||||||
|
- 關閉對話框
|
||||||
|
- 清理表單數據和狀態
|
||||||
|
|
||||||
|
#### 5.3 `handleBatchDeleteByStatus()`
|
||||||
|
- 執行批量刪除操作
|
||||||
|
- 包含完整的驗證、確認、調用API、結果處理流程
|
||||||
|
|
||||||
|
## 用戶操作流程
|
||||||
|
|
||||||
|
1. **打開功能:** 點擊「按狀態刪除」按鈕
|
||||||
|
2. **選擇狀態:** 勾選要刪除的鏈接狀態(可多選)
|
||||||
|
3. **確認操作:** 勾選確認刪除選項
|
||||||
|
4. **提交請求:** 點擊「確認刪除」按鈕
|
||||||
|
5. **二次確認:** 在彈出的確認對話框中確認操作
|
||||||
|
6. **查看結果:** 系統顯示刪除結果並自動刷新列表
|
||||||
|
|
||||||
|
## 錯誤處理
|
||||||
|
|
||||||
|
### 客戶端驗證
|
||||||
|
- 狀態列表為空時提示用戶選擇
|
||||||
|
- 未確認刪除時阻止提交
|
||||||
|
- 表單驗證失敗時顯示相應錯誤信息
|
||||||
|
|
||||||
|
### 服務端錯誤
|
||||||
|
- 網絡錯誤時顯示通用錯誤信息
|
||||||
|
- 服務端返回的具體錯誤信息會顯示給用戶
|
||||||
|
- 部分成功時會顯示成功和失敗的統計信息
|
||||||
|
|
||||||
|
## 移動端優化
|
||||||
|
|
||||||
|
### 界面適配
|
||||||
|
- 對話框寬度自適應移動端屏幕
|
||||||
|
- 狀態選擇框採用縱向佈局
|
||||||
|
- 按鈕尺寸和間距針對觸摸操作優化
|
||||||
|
|
||||||
|
### 樣式優化
|
||||||
|
- 選擇框使用卡片式設計,易於點擊
|
||||||
|
- 選中狀態有明顯的視覺反饋
|
||||||
|
- 支持深色模式適配
|
||||||
|
|
||||||
|
## 安全特性
|
||||||
|
|
||||||
|
1. **權限控制:** 用戶只能刪除自己創建的鏈接
|
||||||
|
2. **操作確認:** 雙重確認機制防止誤操作
|
||||||
|
3. **參數驗證:** 前後端雙重驗證確保數據安全
|
||||||
|
4. **狀態限制:** 限制可選擇的狀態類型和數量
|
||||||
|
|
||||||
|
## 技術特點
|
||||||
|
|
||||||
|
- **響應式設計:** 支持桌面端和移動端
|
||||||
|
- **用戶體驗:** 操作流程清晰,反饋及時
|
||||||
|
- **錯誤處理:** 完善的錯誤捕獲和用戶提示
|
||||||
|
- **代碼復用:** 複用現有的 API 和組件結構
|
||||||
|
- **維護性:** 代碼結構清晰,易於維護和擴展
|
||||||
|
|
||||||
|
這個實現為用戶提供了一個安全、易用的批量刪除功能,大大提高了鏈接管理的效率。
|
||||||
@@ -32,4 +32,10 @@ export function getLinkStatus(codeNo) {
|
|||||||
return http.get(`/api/link/${codeNo}/status`)
|
return http.get(`/api/link/${codeNo}/status`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按状态批量删除链接
|
||||||
|
export function batchDeleteByStatus(payload) {
|
||||||
|
// payload: { statusList: string[], confirmDelete: boolean }
|
||||||
|
return http.post('/api/link/batch-delete-by-status', payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,15 @@
|
|||||||
>
|
>
|
||||||
导出CSV
|
导出CSV
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
@click="showBatchDeleteByStatusDialog"
|
||||||
|
:disabled="!linkList.length"
|
||||||
|
:icon="Delete"
|
||||||
|
>
|
||||||
|
按状态删除
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -178,6 +187,15 @@
|
|||||||
<el-icon><Download /></el-icon>
|
<el-icon><Download /></el-icon>
|
||||||
导出CSV
|
导出CSV
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
@click="showBatchDeleteByStatusDialog"
|
||||||
|
:disabled="!linkList.length"
|
||||||
|
class="mobile-action-btn"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
按状态删除
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -414,6 +432,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 按状态批量删除对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="batchDeleteByStatusDialog.visible"
|
||||||
|
title="按状态批量删除链接"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
class="batch-delete-dialog"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="batchDeleteFormRef"
|
||||||
|
:model="batchDeleteByStatusDialog.form"
|
||||||
|
:rules="batchDeleteByStatusDialog.rules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="选择状态" prop="statusList" required>
|
||||||
|
<el-checkbox-group v-model="batchDeleteByStatusDialog.form.statusList">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="status in statusOptions"
|
||||||
|
:key="status.value"
|
||||||
|
:label="status.value"
|
||||||
|
>
|
||||||
|
{{ status.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
<div class="form-tip">选择要删除的链接状态,可多选</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="confirmDelete" required>
|
||||||
|
<el-checkbox v-model="batchDeleteByStatusDialog.form.confirmDelete">
|
||||||
|
我确认要删除选中状态的所有链接,此操作不可恢复
|
||||||
|
</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="closeBatchDeleteByStatusDialog">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
@click="handleBatchDeleteByStatus"
|
||||||
|
:loading="batchDeleteByStatusDialog.loading"
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -425,7 +491,7 @@ import {
|
|||||||
Plus, Minus, Connection, Refresh, Download,
|
Plus, Minus, Connection, Refresh, Download,
|
||||||
DocumentCopy, Delete
|
DocumentCopy, Delete
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { generateLinks, fetchLinks, deleteLink, batchDeleteLinks } from '@/api/links'
|
import { generateLinks, fetchLinks, deleteLink, batchDeleteLinks, batchDeleteByStatus } from '@/api/links'
|
||||||
import { formatLinkStatus, getLinkStatusType, generateQRCodeUrl, downloadImage, copyToClipboard as copyText, exportToCSV as exportCSV, exportToExcel } from '@/utils/links'
|
import { formatLinkStatus, getLinkStatusType, generateQRCodeUrl, downloadImage, copyToClipboard as copyText, exportToCSV as exportCSV, exportToExcel } from '@/utils/links'
|
||||||
import { LINK_CONFIG, STATUS_CONFIG, EXPORT_CONFIG } from '@/config/links'
|
import { LINK_CONFIG, STATUS_CONFIG, EXPORT_CONFIG } from '@/config/links'
|
||||||
|
|
||||||
@@ -447,6 +513,46 @@ const selectedRows = ref([])
|
|||||||
const showBatchActions = computed(() => selectedRows.value.length > 0)
|
const showBatchActions = computed(() => selectedRows.value.length > 0)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
|
|
||||||
|
// 按状态批量删除相关状态
|
||||||
|
const batchDeleteFormRef = ref()
|
||||||
|
const batchDeleteByStatusDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
form: {
|
||||||
|
statusList: [],
|
||||||
|
confirmDelete: false
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
statusList: [
|
||||||
|
{ required: true, message: '请选择要删除的状态', trigger: 'change' },
|
||||||
|
{ type: 'array', min: 1, message: '至少选择一个状态', trigger: 'change' }
|
||||||
|
],
|
||||||
|
confirmDelete: [
|
||||||
|
{ required: true, message: '请确认删除操作', trigger: 'change' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (!value) {
|
||||||
|
callback(new Error('必须确认删除操作'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'change'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 状态选项
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: 'NEW', label: '新建' },
|
||||||
|
{ value: 'USING', label: '使用中' },
|
||||||
|
{ value: 'LOGGED_IN', label: '已登錄' },
|
||||||
|
{ value: 'COMPLETED', label: '已完成' },
|
||||||
|
{ value: 'REFUNDED', label: '已退款' },
|
||||||
|
{ value: 'EXPIRED', label: '已過期' }
|
||||||
|
]
|
||||||
|
|
||||||
// 生成表单
|
// 生成表单
|
||||||
const generateForm = reactive({
|
const generateForm = reactive({
|
||||||
times: null,
|
times: null,
|
||||||
@@ -810,6 +916,77 @@ const toggleSelection = (item) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按状态批量删除相关方法
|
||||||
|
const showBatchDeleteByStatusDialog = () => {
|
||||||
|
batchDeleteByStatusDialog.visible = true
|
||||||
|
batchDeleteByStatusDialog.form.statusList = []
|
||||||
|
batchDeleteByStatusDialog.form.confirmDelete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeBatchDeleteByStatusDialog = () => {
|
||||||
|
batchDeleteByStatusDialog.visible = false
|
||||||
|
batchDeleteByStatusDialog.loading = false
|
||||||
|
batchDeleteByStatusDialog.form.statusList = []
|
||||||
|
batchDeleteByStatusDialog.form.confirmDelete = false
|
||||||
|
batchDeleteFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBatchDeleteByStatus = async () => {
|
||||||
|
try {
|
||||||
|
// 表单验证
|
||||||
|
await batchDeleteFormRef.value.validate()
|
||||||
|
|
||||||
|
const { statusList, confirmDelete } = batchDeleteByStatusDialog.form
|
||||||
|
|
||||||
|
// 二次确认
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`確定要刪除狀態為 "${statusList.map(s => statusOptions.find(opt => opt.value === s)?.label).join('、')}" 的所有鏈接嗎?此操作不可恢復!`,
|
||||||
|
'批量刪除確認',
|
||||||
|
{
|
||||||
|
confirmButtonText: '確定刪除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
batchDeleteByStatusDialog.loading = true
|
||||||
|
|
||||||
|
// 調用API
|
||||||
|
const response = await batchDeleteByStatus({
|
||||||
|
statusList,
|
||||||
|
confirmDelete
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = response.data
|
||||||
|
|
||||||
|
// 顯示結果
|
||||||
|
if (result.allSuccess) {
|
||||||
|
ElMessage.success(`成功刪除 ${result.successCount} 個鏈接`)
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(
|
||||||
|
`刪除完成:成功 ${result.successCount} 個,失敗 ${result.failedCount} 個`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果有失敗的,顯示詳細信息
|
||||||
|
if (result.failedCount > 0 && result.failedReasons.length > 0) {
|
||||||
|
console.warn('刪除失敗的原因:', result.failedReasons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 關閉對話框並刷新列表
|
||||||
|
closeBatchDeleteByStatusDialog()
|
||||||
|
await getLinkList()
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('按狀態批量刪除失敗:', error)
|
||||||
|
ElMessage.error(error.response?.data?.message || '批量刪除失敗')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
batchDeleteByStatusDialog.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkMobile()
|
checkMobile()
|
||||||
@@ -1259,6 +1436,26 @@ onUnmounted(() => {
|
|||||||
padding: 0 20px 20px 20px;
|
padding: 0 20px 20px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 按状态删除对话框移动端优化 */
|
||||||
|
.batch-delete-dialog :deep(.el-checkbox-group) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-delete-dialog :deep(.el-checkbox) {
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-delete-dialog :deep(.el-checkbox.is-checked) {
|
||||||
|
background: #ecf5ff;
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://192.140.164.137:18080',
|
target: 'http://127.0.0.1:18080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/api/, ''),
|
rewrite: (p) => p.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user