项目相关优化
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue"
|
||||
import {ref, onMounted, computed} from "vue"
|
||||
import AOS from "aos"
|
||||
import "aos/dist/aos.css"
|
||||
import { message, Upload, Select } 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 {
|
||||
changeContentProjectApi,
|
||||
createProjectApi,
|
||||
deleteProjectApi,
|
||||
projectDetailApi,
|
||||
@@ -14,6 +15,9 @@ import {
|
||||
} from "@/api/request/project.js";
|
||||
import {uploadImageApi, uploadVideoApi} from "@/api/request/upload.js";
|
||||
import ProjectDetails from './common/ProjectDetails.vue'
|
||||
import {MdEditor} from 'md-editor-v3';
|
||||
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
|
||||
onMounted(() => {
|
||||
AOS.init({
|
||||
@@ -58,9 +62,9 @@ const getList = () => {
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 0, label: "草稿" },
|
||||
{ value: 1, label: "已发布" },
|
||||
{ value: 2, label: "已下架" },
|
||||
{value: 0, label: "草稿"},
|
||||
{value: 1, label: "已发布"},
|
||||
{value: 2, label: "已下架"},
|
||||
]
|
||||
|
||||
// 分页
|
||||
@@ -87,6 +91,8 @@ const formLoading = ref(false)
|
||||
|
||||
// 图片上传相关
|
||||
const fileList = ref([])
|
||||
const moduleImageFileList = ref([])
|
||||
const flowchartFileList = ref([])
|
||||
const uploading = ref(false)
|
||||
const previewVisible = ref(false)
|
||||
const previewImage = ref("")
|
||||
@@ -131,6 +137,38 @@ const handleUploadChange = (info) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleModuleImageUploadChange = (info) => {
|
||||
if (info.file.status === "uploading") {
|
||||
uploading.value = true
|
||||
return
|
||||
}
|
||||
if (info.file.status === "done") {
|
||||
// 模拟上传成功后获取图片URL
|
||||
uploading.value = false
|
||||
message.success("模块图上传成功")
|
||||
}
|
||||
if (info.file.status === "error") {
|
||||
uploading.value = false
|
||||
message.error("模块图上传失败")
|
||||
}
|
||||
}
|
||||
|
||||
const handleFlowchartUploadChange = (info) => {
|
||||
if (info.file.status === "uploading") {
|
||||
uploading.value = true
|
||||
return
|
||||
}
|
||||
if (info.file.status === "done") {
|
||||
// 模拟上传成功后获取图片URL
|
||||
uploading.value = false
|
||||
message.success("模块图上传成功")
|
||||
}
|
||||
if (info.file.status === "error") {
|
||||
uploading.value = false
|
||||
message.error("模块图上传失败")
|
||||
}
|
||||
}
|
||||
|
||||
const handleScreenshotsUploadChange = (info) => {
|
||||
screenshotsFileList.value = info.fileList
|
||||
|
||||
@@ -186,9 +224,9 @@ const beforeUpload = (file) => {
|
||||
if (!isJpgOrPng) {
|
||||
message.error("只能上传JPG或PNG格式的图片!")
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
const isLt2M = file.size / 1024 / 1024 < 10
|
||||
if (!isLt2M) {
|
||||
message.error("图片大小不能超过2MB!")
|
||||
message.error("图片大小不能超过10MB!")
|
||||
}
|
||||
return isJpgOrPng && isLt2M
|
||||
}
|
||||
@@ -198,34 +236,50 @@ const beforeVideoUpload = (file) => {
|
||||
if (!isVideo) {
|
||||
message.error("只能上传视频文件!")
|
||||
}
|
||||
const isLt50M = file.size / 1024 / 1024 < 50
|
||||
const isLt50M = file.size / 1024 / 1024 < 500
|
||||
if (!isLt50M) {
|
||||
message.error("视频大小不能超过50MB!")
|
||||
message.error("视频大小不能超过500MB!")
|
||||
}
|
||||
return isVideo && isLt50M
|
||||
}
|
||||
|
||||
const customCoverRequest = ({ file, onSuccess }) => {
|
||||
const customCoverRequest = ({file, onSuccess}) => {
|
||||
// 模拟上传
|
||||
uploadImageApi(file).then((res) => {
|
||||
currentItem.value.cover = res.url;
|
||||
onSuccess("ok", { status: "done" })
|
||||
onSuccess("ok", {status: "done"})
|
||||
})
|
||||
}
|
||||
|
||||
const customRequest = ({ file, onSuccess }) => {
|
||||
const customModuleImageRequest = ({file, onSuccess}) => {
|
||||
// 模拟上传
|
||||
uploadImageApi(file).then((res) => {
|
||||
currentItem.value.module_image = res.url;
|
||||
onSuccess("ok", {status: "done"})
|
||||
})
|
||||
}
|
||||
|
||||
const customFlowchartRequest = ({file, onSuccess}) => {
|
||||
// 模拟上传
|
||||
uploadImageApi(file).then((res) => {
|
||||
currentItem.value.flowchart = res.url;
|
||||
onSuccess("ok", {status: "done"})
|
||||
})
|
||||
}
|
||||
|
||||
const customRequest = ({file, onSuccess}) => {
|
||||
// 模拟上传
|
||||
uploadImageApi(file).then((res) => {
|
||||
currentItem.value.screenshots.push(res.url);
|
||||
onSuccess("ok", { status: "done" })
|
||||
onSuccess("ok", {status: "done"})
|
||||
})
|
||||
}
|
||||
|
||||
const customVideoRequest = ({ file, onSuccess }) => {
|
||||
const customVideoRequest = ({file, onSuccess}) => {
|
||||
// 模拟上传
|
||||
uploadVideoApi(file).then((res) => {
|
||||
currentItem.value.video_url = res.url
|
||||
onSuccess("ok", { status: "done" })
|
||||
onSuccess("ok", {status: "done"})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -235,6 +289,7 @@ const openModal = (item = null) => {
|
||||
if (item) {
|
||||
item = JSON.parse(JSON.stringify(item));
|
||||
item.labels = item.label_ids;
|
||||
item.features = item.features_names
|
||||
}
|
||||
currentItem.value = item
|
||||
? JSON.parse(JSON.stringify(item)) // 深拷贝避免直接修改原对象
|
||||
@@ -251,6 +306,9 @@ const openModal = (item = null) => {
|
||||
front: "",
|
||||
server: "",
|
||||
deploy: "",
|
||||
module_image: "",
|
||||
flowchart: "",
|
||||
content: "",
|
||||
screenshots: [],
|
||||
video_url: ""
|
||||
}
|
||||
@@ -261,7 +319,7 @@ const openModal = (item = null) => {
|
||||
fileList.value = [
|
||||
{
|
||||
uid: "-1",
|
||||
name: "cover.jpg",
|
||||
name: "封面",
|
||||
status: "done",
|
||||
url: currentItem.value.cover,
|
||||
},
|
||||
@@ -287,13 +345,37 @@ const openModal = (item = null) => {
|
||||
videoFileList.value = [
|
||||
{
|
||||
uid: "-1",
|
||||
name: "video.mp4",
|
||||
name: "演示视频",
|
||||
status: "done",
|
||||
url: currentItem.value.video_url,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
moduleImageFileList.value = []
|
||||
if (currentItem.value.module_image) {
|
||||
moduleImageFileList.value = [
|
||||
{
|
||||
uid: "-1",
|
||||
name: "模块图",
|
||||
status: "done",
|
||||
url: currentItem.value.module_image,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
flowchartFileList.value = []
|
||||
if (currentItem.value.flowchart) {
|
||||
flowchartFileList.value = [
|
||||
{
|
||||
uid: "-1",
|
||||
name: "流程图",
|
||||
status: "done",
|
||||
url: currentItem.value.flowchart,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
@@ -328,12 +410,12 @@ const handleDelete = () => {
|
||||
}
|
||||
|
||||
const formatPrice = (price) => {
|
||||
return "¥ " + parseFloat(price).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
return "¥ " + parseFloat(price).toLocaleString("zh-CN", {minimumFractionDigits: 2, maximumFractionDigits: 2})
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" })
|
||||
return date.toLocaleDateString("zh-CN", {year: "numeric", month: "2-digit", day: "2-digit"})
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -348,16 +430,10 @@ 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}
|
||||
}
|
||||
} else {
|
||||
createProjectApi(currentItem.value).then((res) => {
|
||||
console.log(res)
|
||||
@@ -371,6 +447,24 @@ const handleSubmit = () => {
|
||||
}, 800)
|
||||
}
|
||||
|
||||
const handleProjectSubmit = () => {
|
||||
formLoading.value = true
|
||||
|
||||
|
||||
if (currentItem.value.id) {
|
||||
changeContentProjectApi(currentItem.value).then((res) => {
|
||||
message.success("更新成功")
|
||||
closeContentModal()
|
||||
getList();
|
||||
})
|
||||
}
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
const closeContentModal = () => {
|
||||
showContentModal.value = false
|
||||
}
|
||||
|
||||
// 标签管理
|
||||
const tagInputValue = ref("")
|
||||
const handleAddTag = () => {
|
||||
@@ -405,12 +499,16 @@ const service = ref("")
|
||||
const deploy = ref("")
|
||||
|
||||
const handleAddFeature = () => {
|
||||
if (featureInputValue.value && !currentItem.value.features.includes(featureInputValue.value)) {
|
||||
if (featureInputValue.value) {
|
||||
if (!currentItem.value.features) {
|
||||
currentItem.value.features = []
|
||||
}
|
||||
currentItem.value.features.push(featureInputValue.value)
|
||||
featureInputValue.value = ""
|
||||
if (currentItem.value.features) {
|
||||
if (!currentItem.value.features.includes(featureInputValue.value)) {
|
||||
currentItem.value.features.push(featureInputValue.value)
|
||||
featureInputValue.value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,8 +524,44 @@ const openDetailModal = (item) => {
|
||||
showDetailModal.value = true
|
||||
})
|
||||
}
|
||||
const showContentModal = ref(false)
|
||||
const openContentModal = (item) => {
|
||||
currentItem.value = {
|
||||
id: item.id,
|
||||
content: item.content || ''
|
||||
}
|
||||
showContentModal.value = true
|
||||
}
|
||||
|
||||
getList();
|
||||
|
||||
const isUpload = ref(false);
|
||||
const editorConfig = ref({
|
||||
preview: {
|
||||
show: false // 确保预览框显示
|
||||
}
|
||||
});
|
||||
// 处理Markdown编辑器的图片上传
|
||||
const handleMdImageUpload = async (
|
||||
files,
|
||||
callback,
|
||||
) => {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const urls = [];
|
||||
for (const file of files) {
|
||||
if (isUpload.value === true) continue;
|
||||
|
||||
isUpload.value = true;
|
||||
const url = await uploadImageApi(file).then((res) => {
|
||||
return res.url;
|
||||
});
|
||||
if (url) urls.push(url);
|
||||
isUpload.value = false;
|
||||
}
|
||||
|
||||
callback(urls);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -478,6 +612,10 @@ getList();
|
||||
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">
|
||||
操作
|
||||
@@ -530,7 +668,10 @@ getList();
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ formatDate(item.createdAt) }}
|
||||
{{ item.created_at }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ item.updated_at }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div class="flex justify-end space-x-2">
|
||||
@@ -547,6 +688,53 @@ getList();
|
||||
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-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="补充详细介绍"
|
||||
@click="openContentModal(item)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none">
|
||||
<path fill="url(#fluentColorAppsListDetail240)"
|
||||
d="M11 6a1 1 0 0 1 1-1h9a1 1 0 1 1 0 2h-9a1 1 0 0 1-1-1"/>
|
||||
<path fill="url(#fluentColorAppsListDetail241)"
|
||||
d="M11 9a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1"/>
|
||||
<path fill="url(#fluentColorAppsListDetail242)"
|
||||
d="M11 15a1 1 0 0 1 1-1h9a1 1 0 1 1 0 2h-9a1 1 0 0 1-1-1"/>
|
||||
<path fill="url(#fluentColorAppsListDetail243)"
|
||||
d="M11 18a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1"/>
|
||||
<path fill="url(#fluentColorAppsListDetail244)"
|
||||
d="M4.25 4A2.25 2.25 0 0 0 2 6.25v2.5A2.25 2.25 0 0 0 4.25 11h2.5A2.25 2.25 0 0 0 9 8.75v-2.5A2.25 2.25 0 0 0 6.75 4zm0 9A2.25 2.25 0 0 0 2 15.25v2.5A2.25 2.25 0 0 0 4.25 20h2.5A2.25 2.25 0 0 0 9 17.75v-2.5A2.25 2.25 0 0 0 6.75 13z"/>
|
||||
<defs>
|
||||
<linearGradient id="fluentColorAppsListDetail240" x1="9.35" x2="20.9" y1="3" y2="19"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#36dff1"/>
|
||||
<stop offset="1" stop-color="#0094f0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fluentColorAppsListDetail241" x1="9.35" x2="20.9" y1="3" y2="19"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#36dff1"/>
|
||||
<stop offset="1" stop-color="#0094f0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fluentColorAppsListDetail242" x1="9.35" x2="20.9" y1="3" y2="19"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#36dff1"/>
|
||||
<stop offset="1" stop-color="#0094f0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fluentColorAppsListDetail243" x1="9.35" x2="20.9" y1="3" y2="19"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#36dff1"/>
|
||||
<stop offset="1" stop-color="#0094f0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="fluentColorAppsListDetail244" x1="3.665" x2="7.232" y1="6.127" y2="19.147"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".125" stop-color="#9c6cfe"/>
|
||||
<stop offset="1" stop-color="#7a41dc"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</g>
|
||||
</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="编辑"
|
||||
@@ -652,7 +840,7 @@ getList();
|
||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div class="mr-10">
|
||||
<div class="mr-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 项目名称 -->
|
||||
<div>
|
||||
@@ -833,99 +1021,173 @@ getList();
|
||||
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>
|
||||
<!-- <MdEditor-->
|
||||
<!-- v-model="currentItem.description"-->
|
||||
<!-- :toolbars-exclude="['github']"-->
|
||||
<!-- class="md-editor-container"-->
|
||||
<!-- editor-id="description-md"-->
|
||||
<!-- theme="dark"-->
|
||||
<!-- :config="editorConfig"-->
|
||||
<!-- @on-upload-img="handleMdImageUpload"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 截图视频 -->
|
||||
<div class="ml-10">
|
||||
<div class="ml-5">
|
||||
|
||||
<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="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="customCoverRequest"
|
||||
@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>
|
||||
<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="customCoverRequest"
|
||||
@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格式,大小不超过10MB
|
||||
</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"
|
||||
<!-- 视频上传 -->
|
||||
<div class="ml-5">
|
||||
<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等常见视频格式,大小不超过500MB
|
||||
</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="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"/>
|
||||
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>
|
||||
<span>点击上传视频</span>
|
||||
</div>
|
||||
预览视频
|
||||
</button>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持MP4、WebM等常见视频格式,大小不超过50MB
|
||||
</div>
|
||||
</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"
|
||||
<!-- 模块图上传 -->
|
||||
<div class="mt-6">
|
||||
<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="moduleImageFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customModuleImageRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleModuleImageUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<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 v-if="moduleImageFileList.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格式,大小不超过10MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 流程图上传 -->
|
||||
<div class="ml-5 mt-6">
|
||||
<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="flowchartFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customFlowchartRequest"
|
||||
@preview="handlePreview"
|
||||
@change="handleFlowchartUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
:maxCount="1"
|
||||
>
|
||||
<div v-if="flowchartFileList.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格式,大小不超过10MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 项目截图上传 -->
|
||||
<div>
|
||||
<div class="mt-6">
|
||||
<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
|
||||
@@ -937,7 +1199,6 @@ getList();
|
||||
@change="handleScreenshotsUploadChange"
|
||||
:show-upload-list="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
multiple
|
||||
:maxCount="10"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
@@ -952,7 +1213,7 @@ getList();
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
支持JPG、PNG格式,大小不超过2MB,可上传多张
|
||||
支持JPG、PNG格式,大小不超过10MB,可上传多张
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -990,6 +1251,81 @@ getList();
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 项目表单模态框 -->
|
||||
<div v-if="showContentModal" class="fixed inset-0 z-50 overflow-y-scroll" 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-5xl 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">
|
||||
补充项目说明
|
||||
</h3>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400 cursor-pointer"
|
||||
@click="closeContentModal"
|
||||
>
|
||||
<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="handleProjectSubmit" @keydown.enter.prevent="event.preventDefault()" class="space-y-6">
|
||||
|
||||
<MdEditor
|
||||
v-model="currentItem.content"
|
||||
:toolbars-exclude="['github']"
|
||||
class="md-editor-container"
|
||||
editor-id="description-md"
|
||||
theme="dark"
|
||||
:config="editorConfig"
|
||||
@on-upload-img="handleMdImageUpload"
|
||||
/>
|
||||
|
||||
<!-- 表单按钮 -->
|
||||
<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="closeContentModal"
|
||||
>
|
||||
取消
|
||||
</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>
|
||||
保存修改
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除确认模态框 -->
|
||||
<div v-if="showDeleteConfirm" class="fixed inset-0 z-50" aria-labelledby="modal-title" role="dialog"
|
||||
@@ -1278,6 +1614,7 @@ getList();
|
||||
font-weight: 600;
|
||||
background-color: var(--color-purple-500);
|
||||
}
|
||||
|
||||
:where(.css-dev-only-do-not-override-1p3hq3p).ant-select-multiple .ant-select-selection-placeholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
Reference in New Issue
Block a user