2025-04-24 16:59:40 +08:00
|
|
|
|
<script setup>
|
2025-04-24 20:47:00 +08:00
|
|
|
|
import { ref, onMounted, computed } from 'vue'
|
|
|
|
|
|
import AOS from 'aos'
|
|
|
|
|
|
import 'aos/dist/aos.css'
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
AOS.init({
|
|
|
|
|
|
duration: 1200,
|
|
|
|
|
|
offset: 120,
|
|
|
|
|
|
easing: 'ease-out-cubic',
|
|
|
|
|
|
once: false
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-04-24 16:59:40 +08:00
|
|
|
|
|
|
|
|
|
|
const dataSource = ref([
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 1,
|
|
|
|
|
|
title: '电商平台',
|
|
|
|
|
|
category: 'Web应用',
|
|
|
|
|
|
tags: ['Vue3', 'Node.js'],
|
|
|
|
|
|
author: 'Admin',
|
2025-04-24 20:47:00 +08:00
|
|
|
|
price: '29,999',
|
|
|
|
|
|
status: '已发布',
|
2025-04-24 16:59:40 +08:00
|
|
|
|
createdAt: '2024-03-20'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-04-24 20:47:00 +08:00
|
|
|
|
id: 2,
|
|
|
|
|
|
title: 'CRM系统',
|
|
|
|
|
|
category: '企业系统',
|
|
|
|
|
|
tags: ['React', 'Express'],
|
|
|
|
|
|
author: 'Admin',
|
|
|
|
|
|
price: '19,999',
|
|
|
|
|
|
status: '已发布',
|
|
|
|
|
|
createdAt: '2024-03-15'
|
2025-04-24 16:59:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-04-24 20:47:00 +08:00
|
|
|
|
id: 3,
|
|
|
|
|
|
title: '数据分析平台',
|
|
|
|
|
|
category: 'Web应用',
|
|
|
|
|
|
tags: ['Vue3', 'Python'],
|
|
|
|
|
|
author: 'Admin',
|
|
|
|
|
|
price: '24,999',
|
|
|
|
|
|
status: '草稿',
|
|
|
|
|
|
createdAt: '2024-03-10'
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
2025-04-24 20:47:00 +08:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const categoryOptions = [
|
|
|
|
|
|
{ value: 'Web应用', label: 'Web应用' },
|
|
|
|
|
|
{ value: '移动端', label: '移动端' },
|
|
|
|
|
|
{ value: '小程序', label: '小程序' },
|
|
|
|
|
|
{ value: '企业系统', label: '企业系统' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const statusOptions = [
|
|
|
|
|
|
{ value: '草稿', label: '草稿' },
|
|
|
|
|
|
{ value: '已发布', label: '已发布' },
|
|
|
|
|
|
{ value: '已下架', label: '已下架' }
|
2025-04-24 16:59:40 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
2025-04-24 20:47:00 +08:00
|
|
|
|
// 分页
|
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
|
const totalItems = ref(dataSource.value.length)
|
|
|
|
|
|
|
|
|
|
|
|
const paginatedData = computed(() => {
|
|
|
|
|
|
const start = (currentPage.value - 1) * pageSize.value
|
|
|
|
|
|
const end = start + pageSize.value
|
|
|
|
|
|
return dataSource.value.slice(start, end)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const changePage = (page) => {
|
|
|
|
|
|
currentPage.value = page
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模态框
|
|
|
|
|
|
const showModal = ref(false)
|
2025-04-24 16:59:40 +08:00
|
|
|
|
const currentItem = ref(null)
|
2025-04-24 20:47:00 +08:00
|
|
|
|
const formLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
const openModal = (item = null) => {
|
|
|
|
|
|
currentItem.value = item ? { ...item } : {
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
category: 'Web应用',
|
|
|
|
|
|
tags: [],
|
|
|
|
|
|
price: '',
|
|
|
|
|
|
status: '草稿',
|
|
|
|
|
|
description: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
showModal.value = true
|
|
|
|
|
|
}
|
2025-04-24 16:59:40 +08:00
|
|
|
|
|
2025-04-24 20:47:00 +08:00
|
|
|
|
const closeModal = () => {
|
|
|
|
|
|
showModal.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确认删除模态框
|
|
|
|
|
|
const showDeleteConfirm = ref(false)
|
|
|
|
|
|
const itemToDelete = ref(null)
|
|
|
|
|
|
|
|
|
|
|
|
const openDeleteConfirm = (id) => {
|
|
|
|
|
|
itemToDelete.value = id
|
|
|
|
|
|
showDeleteConfirm.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const closeDeleteConfirm = () => {
|
|
|
|
|
|
showDeleteConfirm.value = false
|
|
|
|
|
|
itemToDelete.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDelete = () => {
|
|
|
|
|
|
if (itemToDelete.value) {
|
|
|
|
|
|
dataSource.value = dataSource.value.filter(item => item.id !== itemToDelete.value)
|
|
|
|
|
|
totalItems.value = dataSource.value.length
|
|
|
|
|
|
closeDeleteConfirm()
|
|
|
|
|
|
}
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-24 20:47:00 +08:00
|
|
|
|
const handleSubmit = () => {
|
|
|
|
|
|
formLoading.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟API请求
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (currentItem.value.id) {
|
|
|
|
|
|
// 更新现有项目
|
|
|
|
|
|
const index = dataSource.value.findIndex(item => item.id === currentItem.value.id)
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
dataSource.value[index] = { ...currentItem.value }
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 添加新项目
|
|
|
|
|
|
const newId = Math.max(...dataSource.value.map(item => item.id), 0) + 1
|
|
|
|
|
|
dataSource.value.push({
|
|
|
|
|
|
...currentItem.value,
|
|
|
|
|
|
id: newId,
|
|
|
|
|
|
author: 'Admin',
|
|
|
|
|
|
createdAt: new Date().toISOString().split('T')[0]
|
|
|
|
|
|
})
|
|
|
|
|
|
totalItems.value = dataSource.value.length
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
2025-04-24 20:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
formLoading.value = false
|
|
|
|
|
|
closeModal()
|
|
|
|
|
|
}, 800)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标签管理
|
|
|
|
|
|
const tagInputValue = ref('')
|
|
|
|
|
|
const handleAddTag = () => {
|
|
|
|
|
|
if (tagInputValue.value && !currentItem.value.tags.includes(tagInputValue.value)) {
|
|
|
|
|
|
currentItem.value.tags.push(tagInputValue.value)
|
|
|
|
|
|
tagInputValue.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const removeTag = (index) => {
|
|
|
|
|
|
currentItem.value.tags.splice(index, 1)
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-04-24 20:47:00 +08:00
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-3xl shadow-xl p-6" data-aos="fade-up">
|
|
|
|
|
|
<!-- 页面标题和新建按钮 -->
|
|
|
|
|
|
<div class="mb-6 flex justify-between items-center">
|
|
|
|
|
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">项目管理</h2>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
|
|
|
|
|
@click="openModal()"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
|
|
|
|
</svg>
|
2025-04-24 16:59:40 +08:00
|
|
|
|
新建项目
|
2025-04-24 20:47:00 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 表格 -->
|
|
|
|
|
|
<div class="rounded-2xl border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div class="overflow-x-auto" style="height: 75vh">
|
|
|
|
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
|
|
<!-- 表头 -->
|
|
|
|
|
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
项目名称
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
分类
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
标签
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
价格
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
状态
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
创建时间
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
|
|
操作
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 表格内容 -->
|
|
|
|
|
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
|
|
<tr v-for="item in paginatedData" :key="item.id" class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{{ item.title }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
|
|
|
|
|
{{ item.category }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
|
<div class="flex flex-wrap gap-1">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="(tag, index) in item.tags"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="px-2 py-1 bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-300 text-xs font-medium rounded-full"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ tag }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
¥ {{ item.price }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
|
<span
|
|
|
|
|
|
:class="`px-2 py-1 text-xs font-medium rounded-full ${
|
|
|
|
|
|
item.status === '已发布'
|
|
|
|
|
|
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
|
|
|
|
|
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
|
|
|
|
|
}`"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.status }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
|
|
|
|
|
{{ item.createdAt }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
|
|
|
|
<div class="flex justify-end space-x-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="p-1.5 text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors cursor-pointer"
|
|
|
|
|
|
title="查看"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="p-1.5 text-amber-600 hover:text-amber-800 dark:text-amber-400 dark:hover:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-900/20 rounded-lg transition-colors cursor-pointer"
|
|
|
|
|
|
title="编辑"
|
|
|
|
|
|
@click="openModal(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="p-1.5 text-rose-600 hover:text-rose-800 dark:text-rose-400 dark:hover:text-rose-300 hover:bg-rose-50 dark:hover:bg-rose-900/20 rounded-lg transition-colors cursor-pointer"
|
|
|
|
|
|
title="删除"
|
|
|
|
|
|
@click="openDeleteConfirm(item.id)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
|
|
|
|
|
<div class="text-sm text-gray-700 dark:text-gray-300">
|
|
|
|
|
|
共 {{ totalItems }} 条记录
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex space-x-1">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
|
|
|
|
|
:disabled="currentPage === 1"
|
|
|
|
|
|
:class="{ 'opacity-50 cursor-not-allowed': currentPage === 1 }"
|
|
|
|
|
|
@click="currentPage > 1 && changePage(currentPage - 1)"
|
|
|
|
|
|
>
|
|
|
|
|
|
上一页
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="page in Math.ceil(totalItems / pageSize)"
|
|
|
|
|
|
:key="page"
|
|
|
|
|
|
class="px-3 py-1 border rounded-md text-sm font-medium cursor-pointer"
|
|
|
|
|
|
:class="page === currentPage
|
|
|
|
|
|
? 'bg-rose-600 text-white border-rose-600 dark:bg-rose-600 dark:border-rose-600'
|
|
|
|
|
|
: 'border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'"
|
|
|
|
|
|
@click="changePage(page)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ page }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
|
|
|
|
|
:disabled="currentPage === Math.ceil(totalItems / pageSize)"
|
|
|
|
|
|
:class="{ 'opacity-50 cursor-not-allowed': currentPage === Math.ceil(totalItems / pageSize) }"
|
|
|
|
|
|
@click="currentPage < Math.ceil(totalItems / pageSize) && changePage(currentPage + 1)"
|
|
|
|
|
|
>
|
|
|
|
|
|
下一页
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-04-24 16:59:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-04-24 20:47:00 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 项目表单模态框 -->
|
|
|
|
|
|
<div v-if="showModal" class="fixed inset-0 z-50 " aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
|
|
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
|
<!-- 背景遮罩 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="fixed inset-0 transition-opacity backdrop-blur-xl bg-gray-500/80 dark:bg-gray-900/80"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
@click="closeModal"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 使模态框居中 -->
|
|
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模态框内容 -->
|
|
|
|
|
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full relative z-50">
|
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 px-6 pt-5 pb-4 sm:p-6">
|
|
|
|
|
|
<div class="flex justify-between items-center mb-5">
|
|
|
|
|
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white" id="modal-title">
|
|
|
|
|
|
{{ currentItem?.id ? '编辑项目' : '新建项目' }}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 cursor-pointer"
|
|
|
|
|
|
@click="closeModal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<form @submit.prevent="handleSubmit" class="space-y-6">
|
|
|
|
|
|
<!-- 项目名称 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目名称</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="currentItem.title"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
|
|
placeholder="请输入项目名称"
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分类和状态 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目分类</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
v-model="currentItem.category"
|
|
|
|
|
|
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="option in categoryOptions" :key="option.value" :value="option.value">
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目状态</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
v-model="currentItem.status"
|
|
|
|
|
|
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white cursor-pointer"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="option in statusOptions" :key="option.value" :value="option.value">
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 价格 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目价格</label>
|
|
|
|
|
|
<div class="relative">
|
|
|
|
|
|
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500 dark:text-gray-400">¥</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="currentItem.price"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="w-full pl-8 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
|
|
placeholder="请输入项目价格"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 标签 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目标签</label>
|
|
|
|
|
|
<div class="flex flex-wrap gap-2 mb-2">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="(tag, index) in currentItem.tags"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="px-3 py-1.5 bg-rose-50 dark:bg-rose-900/20 text-rose-600 dark:text-rose-300 text-sm font-medium rounded-lg flex items-center"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ tag }}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="ml-1.5 text-rose-500 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-200 cursor-pointer"
|
|
|
|
|
|
@click="removeTag(index)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="tagInputValue"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
|
|
placeholder="输入标签"
|
|
|
|
|
|
@keyup.enter="handleAddTag"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
|
|
|
|
|
@click="handleAddTag"
|
|
|
|
|
|
>
|
|
|
|
|
|
添加
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 项目描述 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目描述</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="currentItem.description"
|
|
|
|
|
|
rows="4"
|
|
|
|
|
|
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-rose-500 focus:border-rose-500 dark:bg-gray-700 dark:text-white"
|
|
|
|
|
|
placeholder="请输入项目描述"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 表单按钮 -->
|
|
|
|
|
|
<div class="flex justify-end space-x-3 pt-4">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="px-5 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer"
|
|
|
|
|
|
@click="closeModal"
|
|
|
|
|
|
>
|
|
|
|
|
|
取消
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
class="px-5 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center cursor-pointer"
|
|
|
|
|
|
:disabled="formLoading"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span v-if="formLoading" class="mr-2">
|
|
|
|
|
|
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
|
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
|
|
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{{ currentItem?.id ? '保存修改' : '创建项目' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 删除确认模态框 -->
|
|
|
|
|
|
<div v-if="showDeleteConfirm" class="fixed inset-0 z-50" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
|
|
|
|
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
|
|
<!-- 背景遮罩 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="fixed inset-0 transition-opacity backdrop-blur-xl bg-gray-500/80 dark:bg-gray-900/80"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
@click="closeDeleteConfirm"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 使模态框居中 -->
|
|
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模态框内容 -->
|
|
|
|
|
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full relative z-50">
|
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
|
|
|
|
<div class="sm:flex sm:items-start">
|
|
|
|
|
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900/30 sm:mx-0 sm:h-10 sm:w-10">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
|
|
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white" id="modal-title">
|
|
|
|
|
|
确认删除
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div class="mt-2">
|
|
|
|
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
|
|
|
|
确定要删除这个项目吗?此操作无法撤销。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm cursor-pointer"
|
|
|
|
|
|
@click="handleDelete"
|
|
|
|
|
|
>
|
|
|
|
|
|
确认
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm cursor-pointer"
|
|
|
|
|
|
@click="closeDeleteConfirm"
|
|
|
|
|
|
>
|
|
|
|
|
|
取消
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-04-24 16:59:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2025-04-24 20:47:00 +08:00
|
|
|
|
/* 自定义样式 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 毛玻璃效果增强 */
|
|
|
|
|
|
.backdrop-blur-xl {
|
|
|
|
|
|
backdrop-filter: blur(16px);
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
2025-04-24 20:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
.backdrop-blur-md {
|
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.backdrop-blur-sm {
|
|
|
|
|
|
backdrop-filter: blur(4px);
|
2025-04-24 16:59:40 +08:00
|
|
|
|
}
|
2025-04-24 20:47:00 +08:00
|
|
|
|
</style>
|