From f005953af9149feb89578ee387d225808d3b20b4 Mon Sep 17 00:00:00 2001 From: lq Date: Tue, 6 May 2025 16:59:12 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=AE=A1=E7=90=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E9=83=A8=E5=88=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/request/category.js | 19 + src/api/request/label.js | 19 + src/api/request/project.js | 19 + src/api/request/upload.js | 10 + src/views/admin/Projects.vue | 780 ++++++++++++++++++++--------------- 5 files changed, 509 insertions(+), 338 deletions(-) create mode 100644 src/api/request/category.js create mode 100644 src/api/request/label.js create mode 100644 src/api/request/project.js create mode 100644 src/api/request/upload.js diff --git a/src/api/request/category.js b/src/api/request/category.js new file mode 100644 index 0000000..27d9a1e --- /dev/null +++ b/src/api/request/category.js @@ -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) +} \ No newline at end of file diff --git a/src/api/request/label.js b/src/api/request/label.js new file mode 100644 index 0000000..9e5f034 --- /dev/null +++ b/src/api/request/label.js @@ -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) +} \ No newline at end of file diff --git a/src/api/request/project.js b/src/api/request/project.js new file mode 100644 index 0000000..37f3296 --- /dev/null +++ b/src/api/request/project.js @@ -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) +} \ No newline at end of file diff --git a/src/api/request/upload.js b/src/api/request/upload.js new file mode 100644 index 0000000..f7d7d88 --- /dev/null +++ b/src/api/request/upload.js @@ -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) +} \ No newline at end of file diff --git a/src/views/admin/Projects.vue b/src/views/admin/Projects.vue index acecc4c..acc6513 100644 --- a/src/views/admin/Projects.vue +++ b/src/views/admin/Projects.vue @@ -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();