项目管理接口部分完成
This commit is contained in:
19
src/api/request/category.js
Normal file
19
src/api/request/category.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {get, post} from '../request.js'
|
||||
|
||||
const prefix = 'category'
|
||||
|
||||
export function categoryListApi(credentials) {
|
||||
return get(`${prefix}/list`, credentials)
|
||||
}
|
||||
export function categoryOptionApi(credentials) {
|
||||
return get(`${prefix}/option`, credentials)
|
||||
}
|
||||
export function createCategoryApi(credentials) {
|
||||
return post(`${prefix}/create`, credentials)
|
||||
}
|
||||
export function updateCategoryApi(credentials) {
|
||||
return post(`${prefix}/update`, credentials)
|
||||
}
|
||||
export function deleteCategoryApi(credentials) {
|
||||
return post(`${prefix}/delete`, credentials)
|
||||
}
|
||||
19
src/api/request/label.js
Normal file
19
src/api/request/label.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {get, post} from '../request.js'
|
||||
|
||||
const prefix = 'label'
|
||||
|
||||
export function labelListApi(credentials) {
|
||||
return get(`${prefix}/list`, credentials)
|
||||
}
|
||||
export function labelOptionApi(credentials) {
|
||||
return get(`${prefix}/option`, credentials)
|
||||
}
|
||||
export function createLabelApi(credentials) {
|
||||
return post(`${prefix}/create`, credentials)
|
||||
}
|
||||
export function updateLabelApi(credentials) {
|
||||
return post(`${prefix}/update`, credentials)
|
||||
}
|
||||
export function deleteLabelApi(credentials) {
|
||||
return post(`${prefix}/delete`, credentials)
|
||||
}
|
||||
19
src/api/request/project.js
Normal file
19
src/api/request/project.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {get, post} from '../request.js'
|
||||
|
||||
const prefix = 'project'
|
||||
|
||||
export function labelListApi(credentials) {
|
||||
return get(`${prefix}/list`, credentials)
|
||||
}
|
||||
export function labelOptionApi(credentials) {
|
||||
return get(`${prefix}/option`, credentials)
|
||||
}
|
||||
export function createProjectApi(credentials) {
|
||||
return post(`${prefix}/create`, credentials)
|
||||
}
|
||||
export function updateProjectApi(credentials) {
|
||||
return post(`${prefix}/update`, credentials)
|
||||
}
|
||||
export function deleteProjectApi(credentials) {
|
||||
return post(`${prefix}/delete`, credentials)
|
||||
}
|
||||
10
src/api/request/upload.js
Normal file
10
src/api/request/upload.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {upload} from '../request.js'
|
||||
|
||||
const prefix = 'upload'
|
||||
|
||||
export function uploadImageApi(credentials) {
|
||||
return upload(`${prefix}/image`, credentials)
|
||||
}
|
||||
export function uploadVideoApi(credentials) {
|
||||
return upload(`${prefix}/video`, credentials)
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
import { ref, onMounted, computed } from "vue"
|
||||
import AOS from "aos"
|
||||
import "aos/dist/aos.css"
|
||||
import { message, Upload } from "ant-design-vue"
|
||||
import { message, Upload, Select } from "ant-design-vue"
|
||||
import {labelOptionApi} from "@/api/request/label.js";
|
||||
import {categoryOptionApi} from "@/api/request/category.js";
|
||||
import {createProjectApi, labelListApi, updateProjectApi} from "@/api/request/project.js";
|
||||
import {uploadImageApi, uploadVideoApi} from "@/api/request/upload.js";
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
@@ -14,32 +18,35 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
// 分类数据
|
||||
const categoryOptions = [
|
||||
{ id: 1, name: "Web应用", value: "Web应用" },
|
||||
{ id: 2, name: "移动端", value: "移动端" },
|
||||
{ id: 3, name: "小程序", value: "小程序" },
|
||||
{ id: 4, name: "企业系统", value: "企业系统" },
|
||||
]
|
||||
const categoryOptions = ref([])
|
||||
|
||||
// 标签数据
|
||||
const allTags = [
|
||||
{ id: 1, name: "Vue3" },
|
||||
{ id: 2, name: "Node.js" },
|
||||
{ id: 3, name: "TypeScript" },
|
||||
{ id: 4, name: "React" },
|
||||
{ id: 5, name: "Express" },
|
||||
{ id: 6, name: "PHP" },
|
||||
{ id: 7, name: "Python" },
|
||||
{ id: 8, name: "MongoDB" },
|
||||
{ id: 9, name: "Docker" },
|
||||
]
|
||||
// 表单相关
|
||||
const tagsOption = ref([]);
|
||||
|
||||
const getLabelOption = () => {
|
||||
labelOptionApi().then((res) => {
|
||||
tagsOption.value = res;
|
||||
})
|
||||
}
|
||||
|
||||
const getCategoryOption = () => {
|
||||
categoryOptionApi().then((res) => {
|
||||
categoryOptions.value = res;
|
||||
})
|
||||
}
|
||||
|
||||
const dataSource = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "电商平台",
|
||||
category: categoryOptions[0],
|
||||
tags: [allTags[0], allTags[1], allTags[5]],
|
||||
category: { id: 1, name: "Web应用", value: "Web应用" },
|
||||
tags: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Vue",
|
||||
value: "Vue",
|
||||
}
|
||||
],
|
||||
author: "Admin",
|
||||
price: 29999,
|
||||
status: "已发布",
|
||||
@@ -52,13 +59,19 @@ const dataSource = ref([
|
||||
"https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280",
|
||||
"https://img2.baidu.com/it/u=1629222614,1358629025&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"
|
||||
],
|
||||
video: "http://d-jy.nailaoyun.cn//storage/video//20250327/2cc1abf9bd69410ce7c69660daf54260.mp4"
|
||||
video_url: "http://d-jy.nailaoyun.cn//storage/video//20250327/2cc1abf9bd69410ce7c69660daf54260.mp4"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "CRM系统",
|
||||
category: categoryOptions[3],
|
||||
tags: [allTags[3], allTags[4]],
|
||||
category: { id: 4, name: "企业系统", value: "企业系统" },
|
||||
tags: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Vue",
|
||||
value: "Vue",
|
||||
}
|
||||
],
|
||||
author: "Admin",
|
||||
price: 19999,
|
||||
status: "已发布",
|
||||
@@ -70,13 +83,19 @@ const dataSource = ref([
|
||||
screenshots: [
|
||||
"https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280"
|
||||
],
|
||||
video: ""
|
||||
video_url: ""
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "数据分析平台",
|
||||
category: categoryOptions[0],
|
||||
tags: [allTags[0], allTags[6]],
|
||||
category: { id: 1, name: "Web应用", value: "Web应用" },
|
||||
tags: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Vue",
|
||||
value: "Vue",
|
||||
}
|
||||
],
|
||||
author: "Admin",
|
||||
price: 24999,
|
||||
status: "草稿",
|
||||
@@ -86,20 +105,34 @@ const dataSource = ref([
|
||||
features: ["实时数据仪表盘", "自定义报表生成", "数据导入导出"],
|
||||
technicalStack: ["前端:Vue3 + ECharts", "后端:Python + Django", "部署:Docker + Kubernetes"],
|
||||
screenshots: [],
|
||||
video: ""
|
||||
video_url: ""
|
||||
},
|
||||
])
|
||||
|
||||
const getList = () => {
|
||||
labelListApi({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
}).then((res) => {
|
||||
dataSource.value = res.items;
|
||||
currentPage.value = res.page;
|
||||
pageSize.value = res.size;
|
||||
totalItems.value = res.total;
|
||||
pageCount.value = res.page_count;
|
||||
})
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: "草稿", label: "草稿" },
|
||||
{ value: "已发布", label: "已发布" },
|
||||
{ value: "已下架", label: "已下架" },
|
||||
{ value: 0, label: "草稿" },
|
||||
{ value: 1, label: "已发布" },
|
||||
{ value: 2, label: "已下架" },
|
||||
]
|
||||
|
||||
// 分页
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalItems = ref(dataSource.value.length)
|
||||
const pageSize = ref(20)
|
||||
const totalItems = ref(0)
|
||||
const pageCount = ref(0)
|
||||
|
||||
const paginatedData = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
@@ -138,7 +171,7 @@ const handlePreview = (file) => {
|
||||
}
|
||||
|
||||
const handleVideoPreview = () => {
|
||||
videoPreviewUrl.value = currentItem.value.video
|
||||
videoPreviewUrl.value = currentItem.value.video_url
|
||||
videoPreviewVisible.value = true
|
||||
}
|
||||
|
||||
@@ -173,12 +206,6 @@ const handleScreenshotsUploadChange = (info) => {
|
||||
currentItem.value.screenshots = []
|
||||
}
|
||||
|
||||
// 确保不重复添加
|
||||
const newUrl = "https://img2.baidu.com/it/u=1629222614,1358629025&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"
|
||||
if (!currentItem.value.screenshots.includes(newUrl)) {
|
||||
currentItem.value.screenshots.push(newUrl)
|
||||
}
|
||||
|
||||
screenshotsUploading.value = false
|
||||
message.success("截图上传成功")
|
||||
}
|
||||
@@ -191,13 +218,14 @@ const handleScreenshotsUploadChange = (info) => {
|
||||
const handleVideoUploadChange = (info) => {
|
||||
videoFileList.value = info.fileList.slice(-1) // 只保留最后一个文件
|
||||
|
||||
console.log(info, 'ssssssss')
|
||||
if (info.file.status === "uploading") {
|
||||
videoUploading.value = true
|
||||
return
|
||||
}
|
||||
if (info.file.status === "done") {
|
||||
// 模拟上传成功后获取视频URL
|
||||
currentItem.value.video = "http://d-jy.nailaoyun.cn//storage/video//20250327/2cc1abf9bd69410ce7c69660daf54260.mp4"
|
||||
// currentItem.value.video_url = info.file.response.url
|
||||
videoUploading.value = false
|
||||
message.success("视频上传成功")
|
||||
}
|
||||
@@ -239,29 +267,46 @@ const beforeVideoUpload = (file) => {
|
||||
return isVideo && isLt50M
|
||||
}
|
||||
|
||||
const customRequest = ({ onSuccess }) => {
|
||||
const customRequest = ({ file, onSuccess }) => {
|
||||
// 模拟上传
|
||||
setTimeout(() => {
|
||||
uploadImageApi(file).then((res) => {
|
||||
currentItem.value.screenshots.push(res.url);
|
||||
onSuccess("ok", { status: "done" })
|
||||
}, 800)
|
||||
})
|
||||
}
|
||||
|
||||
const customVideoRequest = ({ file, onSuccess }) => {
|
||||
// 模拟上传
|
||||
uploadVideoApi(file).then((res) => {
|
||||
currentItem.value.video_url = res.url
|
||||
onSuccess("ok", { status: "done" })
|
||||
})
|
||||
}
|
||||
|
||||
const openModal = (item = null) => {
|
||||
getLabelOption();
|
||||
getCategoryOption();
|
||||
if (item) {
|
||||
item = JSON.parse(JSON.stringify(item));
|
||||
item.labels = item.label_ids;
|
||||
}
|
||||
currentItem.value = item
|
||||
? JSON.parse(JSON.stringify(item)) // 深拷贝避免直接修改原对象
|
||||
: {
|
||||
title: "",
|
||||
category: categoryOptions[0],
|
||||
category_id: 1,
|
||||
tags: [],
|
||||
price: "",
|
||||
status: "草稿",
|
||||
status: 0,
|
||||
preferential_price: '',
|
||||
description: "",
|
||||
cover: "",
|
||||
author: "Admin",
|
||||
features: [],
|
||||
technicalStack: [],
|
||||
front: "",
|
||||
server: "",
|
||||
deploy: "",
|
||||
screenshots: [],
|
||||
video: ""
|
||||
video_url: ""
|
||||
}
|
||||
|
||||
// 重置文件列表
|
||||
@@ -292,13 +337,13 @@ const openModal = (item = null) => {
|
||||
|
||||
// 重置视频文件列表
|
||||
videoFileList.value = []
|
||||
if (currentItem.value.video) {
|
||||
if (currentItem.value.video_url) {
|
||||
videoFileList.value = [
|
||||
{
|
||||
uid: "-1",
|
||||
name: "video.mp4",
|
||||
status: "done",
|
||||
url: currentItem.value.video,
|
||||
url: currentItem.value.video_url,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -352,27 +397,27 @@ const handleSubmit = () => {
|
||||
}
|
||||
|
||||
if (currentItem.value.id) {
|
||||
updateProjectApi(currentItem.value).then((res) => {
|
||||
|
||||
message.success("项目更新成功")
|
||||
closeModal()
|
||||
getList();
|
||||
})
|
||||
// 更新现有项目
|
||||
const index = dataSource.value.findIndex((item) => item.id === currentItem.value.id)
|
||||
if (index !== -1) {
|
||||
dataSource.value[index] = { ...currentItem.value }
|
||||
}
|
||||
message.success("项目更新成功")
|
||||
} 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(),
|
||||
createProjectApi(currentItem.value).then((res) => {
|
||||
console.log(res)
|
||||
message.success("项目创建成功")
|
||||
closeModal()
|
||||
getList();
|
||||
})
|
||||
totalItems.value = dataSource.value.length
|
||||
message.success("项目创建成功")
|
||||
}
|
||||
|
||||
formLoading.value = false
|
||||
closeModal()
|
||||
}, 800)
|
||||
}
|
||||
|
||||
@@ -381,7 +426,7 @@ const tagInputValue = ref("")
|
||||
const handleAddTag = () => {
|
||||
if (tagInputValue.value) {
|
||||
// 检查是否已存在该标签
|
||||
const existingTag = allTags.find((tag) => tag.name.toLowerCase() === tagInputValue.value.toLowerCase())
|
||||
const existingTag = tagsOption.find((tag) => tag.name.toLowerCase() === tagInputValue.value.toLowerCase())
|
||||
|
||||
// 如果存在且未添加到当前项目,则添加
|
||||
if (existingTag && !currentItem.value.tags.some((tag) => tag.id === existingTag.id)) {
|
||||
@@ -389,9 +434,9 @@ const handleAddTag = () => {
|
||||
}
|
||||
// 如果不存在,创建新标签并添加
|
||||
else if (!existingTag) {
|
||||
const newTagId = Math.max(...allTags.map((tag) => tag.id), 0) + 1
|
||||
const newTagId = Math.max(...tagsOption.map((tag) => tag.id), 0) + 1
|
||||
const newTag = { id: newTagId, name: tagInputValue.value }
|
||||
allTags.push(newTag)
|
||||
tagsOption.push(newTag)
|
||||
currentItem.value.tags.push(newTag)
|
||||
}
|
||||
|
||||
@@ -405,7 +450,9 @@ const removeTag = (index) => {
|
||||
|
||||
// 功能和技术栈管理
|
||||
const featureInputValue = ref("")
|
||||
const techStackInputValue = ref("")
|
||||
const front = ref("")
|
||||
const service = ref("")
|
||||
const deploy = ref("")
|
||||
|
||||
const handleAddFeature = () => {
|
||||
if (featureInputValue.value && !currentItem.value.features.includes(featureInputValue.value)) {
|
||||
@@ -421,19 +468,7 @@ const removeFeature = (index) => {
|
||||
currentItem.value.features.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleAddTechStack = () => {
|
||||
if (techStackInputValue.value && !currentItem.value.technicalStack.includes(techStackInputValue.value)) {
|
||||
if (!currentItem.value.technicalStack) {
|
||||
currentItem.value.technicalStack = []
|
||||
}
|
||||
currentItem.value.technicalStack.push(techStackInputValue.value)
|
||||
techStackInputValue.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
const removeTechStack = (index) => {
|
||||
currentItem.value.technicalStack.splice(index, 1)
|
||||
}
|
||||
getList();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -502,7 +537,7 @@ const removeTechStack = (index) => {
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="(tag, index) in item.tags"
|
||||
v-for="(tag, index) in item.labels"
|
||||
: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"
|
||||
>
|
||||
@@ -516,14 +551,14 @@ const removeTechStack = (index) => {
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
:class="`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
item.status === '已发布'
|
||||
item.status === 0
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||
: item.status === '已下架'
|
||||
: item.status === 1
|
||||
? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300'
|
||||
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
||||
}`"
|
||||
>
|
||||
{{ item.status }}
|
||||
{{ item.status_text }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
@@ -580,7 +615,7 @@ const removeTechStack = (index) => {
|
||||
上一页
|
||||
</button>
|
||||
<button
|
||||
v-for="page in Math.ceil(totalItems / pageSize)"
|
||||
v-for="page in pageCount"
|
||||
:key="page"
|
||||
class="px-3 py-1 border rounded-md text-sm font-medium cursor-pointer"
|
||||
:class="page === currentPage
|
||||
@@ -592,9 +627,9 @@ const removeTechStack = (index) => {
|
||||
</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)"
|
||||
:disabled="currentPage === pageCount"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': currentPage === pageCount }"
|
||||
@click="currentPage < pageCount && changePage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
@@ -633,116 +668,142 @@ const removeTechStack = (index) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- 基本信息 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-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>
|
||||
<form @submit.prevent="handleSubmit" @keydown.enter.prevent="event.preventDefault()" class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||
|
||||
<!-- 价格 -->
|
||||
<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 class="mr-10">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-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>
|
||||
<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.id" :value="option">
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</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>
|
||||
<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.preferential_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.name }}
|
||||
<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)"
|
||||
<!-- 分类 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目分类</label>
|
||||
<select
|
||||
v-model="currentItem.category_id"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<option v-for="option in categoryOptions" :key="option.id" :value="option.id">
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</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>
|
||||
<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>
|
||||
<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">
|
||||
<!-- 技术栈 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">前端技术栈</label>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="currentItem.front"
|
||||
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="输入前端技术栈"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">后端技术栈</label>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="currentItem.service"
|
||||
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="输入后端技术栈"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">部署架构</label>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="currentItem.deploy"
|
||||
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="输入部署架构"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- 标签 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目标签</label>
|
||||
<div class="w-full px-1 py-1 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">
|
||||
<Select
|
||||
class="w-full"
|
||||
v-model:value="currentItem.labels"
|
||||
:options="tagsOption"
|
||||
mode="multiple"
|
||||
placeholder="请选择标签"
|
||||
:fieldNames="{
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}"
|
||||
/>
|
||||
</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="(feature, index) in currentItem.features"
|
||||
:key="index"
|
||||
@@ -759,164 +820,148 @@ const removeTechStack = (index) => {
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="featureInputValue"
|
||||
type="text"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="输入功能"
|
||||
@keyup.enter="handleAddFeature"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
||||
@click="handleAddFeature"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</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="(tech, index) in currentItem.technicalStack"
|
||||
:key="index"
|
||||
class="px-3 py-1.5 bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-300 text-sm font-medium rounded-lg flex items-center"
|
||||
>
|
||||
{{ tech }}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1.5 text-purple-500 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-200 cursor-pointer"
|
||||
@click="removeTechStack(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="techStackInputValue"
|
||||
type="text"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-purple-500 focus:border-purple-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="输入技术栈"
|
||||
@keyup.enter="handleAddTechStack"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
||||
@click="handleAddTechStack"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目封面上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目封面</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<div v-if="fileList.length < 1">
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传封面</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="featureInputValue"
|
||||
type="text"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-l-xl focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="输入功能"
|
||||
@keyup.enter="handleAddFeature"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-r-xl transition-colors cursor-pointer"
|
||||
@click="handleAddFeature"
|
||||
>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持JPG、PNG格式,大小不超过2MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目截图上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目截图</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="screenshotsFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleScreenshotsUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
multiple
|
||||
>
|
||||
|
||||
<!-- 项目描述 -->
|
||||
<div>
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传截图</span>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 截图视频 -->
|
||||
<div class="ml-10">
|
||||
|
||||
|
||||
<!-- 项目封面上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目封面</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<div v-if="fileList.length < 1">
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传封面</span>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持JPG、PNG格式,大小不超过2MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">演示视频</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="videoFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeVideoUpload"
|
||||
:custom-request="customVideoRequest"
|
||||
@change="handleVideoUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: false, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<div v-if="videoFileList.length < 1">
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传视频</span>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持MP4、WebM等常见视频格式,大小不超过50MB
|
||||
</div>
|
||||
|
||||
<!-- 视频预览按钮 -->
|
||||
<div v-if="currentItem.video_url" class="mt-3">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-1.5 bg-gray-100 dark:bg-gray-600 text-gray-700 dark:text-gray-200 text-sm font-medium rounded-lg flex items-center hover:bg-gray-200 dark:hover:bg-gray-500 transition-colors cursor-pointer"
|
||||
@click="handleVideoPreview"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
预览视频
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 项目截图上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">项目截图</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="screenshotsFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleScreenshotsUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
multiple
|
||||
>
|
||||
<div>
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传截图</span>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持JPG、PNG格式,大小不超过2MB,可上传多张
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持JPG、PNG格式,大小不超过2MB,可上传多张
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频上传 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">演示视频</label>
|
||||
<div class="border border-gray-300 dark:border-gray-600 rounded-xl p-4 dark:bg-gray-700">
|
||||
<Upload
|
||||
v-model:file-list="videoFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeVideoUpload"
|
||||
:custom-request="customRequest"
|
||||
@change="handleVideoUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: false, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<div v-if="videoFileList.length < 1">
|
||||
<div class="ant-upload-text flex flex-col items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mb-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>点击上传视频</span>
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持MP4、WebM等常见视频格式,大小不超过50MB
|
||||
</div>
|
||||
|
||||
<!-- 视频预览按钮 -->
|
||||
<div v-if="currentItem.video" class="mt-3">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-1.5 bg-gray-100 dark:bg-gray-600 text-gray-700 dark:text-gray-200 text-sm font-medium rounded-lg flex items-center hover:bg-gray-200 dark:hover:bg-gray-500 transition-colors cursor-pointer"
|
||||
@click="handleVideoPreview"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
预览视频
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单按钮 -->
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
@@ -1046,6 +1091,7 @@ const removeTechStack = (index) => {
|
||||
|
||||
<!-- 模态框内容 -->
|
||||
<div class="inline-block align-bottom bg-transparent rounded-lg text-left overflow-hidden transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full relative z-50">
|
||||
{{videoPreviewUrl}}
|
||||
<div class="relative">
|
||||
<button
|
||||
class="absolute top-2 right-2 p-2 rounded-full bg-gray-800/50 text-white hover:bg-gray-700/50 transition-colors cursor-pointer z-10"
|
||||
@@ -1175,4 +1221,62 @@ const removeTechStack = (index) => {
|
||||
background-color: rgba(75, 85, 99, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
:deep .ant-select-show-search:where(.css-dev-only-do-not-override-1p3hq3p).ant-select:not(.ant-select-customize-input) .ant-select-selector {
|
||||
cursor: text;
|
||||
border-color: #364153;
|
||||
background-color: #364153;
|
||||
color: white;
|
||||
}
|
||||
:deep :where(.css-dev-only-do-not-override-1p3hq3p).ant-select-multiple .ant-select-selection-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: none;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
height: 24px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
line-height: 22px;
|
||||
background: rgba(255, 32, 86, 0.35);
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
transition: font-size 0.3s, line-height 0.3s, height 0.3s;
|
||||
user-select: none;
|
||||
margin-inline-end: 4px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: 4px;
|
||||
border-color: var(--color-gray-600);
|
||||
}
|
||||
|
||||
:deep :where(.css-dev-only-do-not-override-1p3hq3p).ant-select-dropdown .ant-select-item-option-active:not(.ant-select-item-option-disabled) {
|
||||
//background-color: #364153;
|
||||
background-color: var(--color-gray-600);
|
||||
}
|
||||
:deep :where(.css-dev-only-do-not-override-1p3hq3p).ant-select-dropdown {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
list-style: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
z-index: 1050;
|
||||
overflow: hidden;
|
||||
font-variant: initial;
|
||||
background-color: var(--color-gray-600) !important;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep :where(.css-dev-only-do-not-override-1p3hq3p).ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-weight: 600;
|
||||
background-color: var(--color-purple-500);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user