新增软件概述;新增全局loading插件

This commit is contained in:
2026-02-02 17:34:03 +08:00
parent 72c080230f
commit 4a50e0ef48
9 changed files with 291 additions and 27 deletions

View File

@@ -110,5 +110,27 @@ export default {
id: projectId
}
})
},
/**
* 新增或者修改软件概述
* @returns 返回新增或修改是否成功
*/
postSoftSummary(data) {
return request({
url: "/testmanage/project/soft_summary/",
method: "post",
data: data
})
},
/**
* 获取项目的软件概述
* @returns 返回软件概述数据
*/
getSoftSummary(id) {
return request({
url: "/testmanage/project/get_soft_summary/",
method: "get",
params: { id: id }
})
}
}

View File

@@ -0,0 +1,64 @@
<template>
<div v-if="visible" class="global-loading">
<div class="spinner"></div>
<div v-if="text" class="loading-text">{{ text }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
const visible = ref(false)
const text = ref("加载中...")
const show = (loadingText = "加载中...") => {
text.value = loadingText
visible.value = true
}
const hide = () => {
visible.value = false
}
// 暴露方法供外部调用
defineExpose({ show, hide })
</script>
<style scoped>
.global-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0.8);
z-index: 9999;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: rgb(var(--blue-6));
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
.loading-text {
margin-top: 10px;
color: rgb(var(--blue-6));
user-select: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,5 +1,5 @@
import { TableColumnData } from "@arco-design/web-vue"
import { ref, reactive } from "vue"
import { ref, reactive, onUnmounted } from "vue"
// 粘贴图片组件
import ImageInput from "../ImageInput/index.vue"
// wordlike组件
@@ -44,7 +44,7 @@ export default function useTable() {
case "image":
return <ImageInput v-model={data.value[rowIndex].content} v-model:fontnote={data.value[rowIndex].fontnote}></ImageInput>
case "table":
return <WordLikeTable></WordLikeTable>
return <WordLikeTable v-model={data.value[rowIndex].content} v-model:fontnote={data.value[rowIndex].fontnote}></WordLikeTable>
}
}
},
@@ -66,14 +66,19 @@ export default function useTable() {
}
])
// 卸载时清空数据
const handleOnClose = () => {
data.value = [{ ...initalRowData }]
}
// 数据定义 - 测试
const data = ref([
const data = ref<
{
type: "text",
content: "这是数据内容",
fontnote: ""
}
])
type: string
content: string | string[][]
fontnote: string
}[]
>([])
// 单行初始内容-并设置数据类型
const initalRowData = {
@@ -104,8 +109,16 @@ export default function useTable() {
// 新增表格
const addTableRow = () => {
data.value.push({ type: "table", content: "", fontnote: "" })
data.value.push({
type: "table",
content: [
["", "", ""],
["", "", ""],
["", "", ""]
],
fontnote: ""
})
}
return { columns, data, handleChange, addTextRow, addPicRow, addTableRow }
return { columns, data, handleChange, addTextRow, addPicRow, addTableRow, handleOnClose }
}

View File

@@ -1,7 +1,17 @@
<template>
<div class="project-modal-container">
<a-modal v-model:visible="visible" width="70%" draggable :on-before-ok="handleSyncOk">
<template #title>{{ "软件概述" }}</template>
<a-modal
v-model:visible="visible"
width="80%"
draggable
:on-before-ok="handleSyncOk"
unmount-on-close
ok-text="确认保存"
cancel-text="关闭不保存"
:maskClosable="false"
@close="handleOnClose"
>
<template #title>{{ title }}</template>
<div class="mb-2">
<a-space>
<a-dropdown>
@@ -23,17 +33,44 @@
<script setup lang="ts">
import { ref } from "vue"
import useTable from "./hooks/useTable"
import projectApi from "@/api/project/project"
import { useRoute } from "vue-router"
import { Message } from "@arco-design/web-vue"
import { getCurrentInstance } from "vue"
const { proxy } = getCurrentInstance() as any
const route = useRoute()
const visible = ref(false)
const title = ref("软件概述-新增")
const { columns, data, handleChange, addTextRow, addPicRow, addTableRow } = useTable()
const { columns, data, handleChange, addTextRow, addPicRow, addTableRow, handleOnClose } = useTable()
const handleSyncOk = async () => {
try {
await projectApi.postSoftSummary({ id: route.query.id, data: data.value })
visible.value = false
Message.success("保存成功")
} catch (e) {
Message.error("提交时发送错误,请联系管理员")
}
return false
}
const open = async () => {
visible.value = true
proxy?.$loading?.show("数据加载中...")
try {
const res = await projectApi.getSoftSummary(route.query.id)
const code = res.code // 25001表示有数据25002表示没有数据
title.value = code === 25001 ? "软件概述-修改" : "软件概述-新增"
data.value = res.data
visible.value = true
} catch (e) {
Message.error("获取软件概述信息失败")
} finally {
proxy?.$loading?.hide()
}
}
defineExpose({ open })

View File

@@ -1,14 +1,122 @@
<template>
<!-- 完成自定义表格 -->
<div class="word-like-container">
111
<div class="fontnote">
<a-space class="w-full">
<span>题注</span>
<a-input v-model="fontnote" :style="{ width: '500px' }"></a-input>
</a-space>
</div>
<div class="arco-table arco-table-size-large arco-table-border arco-table-stripe arco-table-hover">
<div class="arco-table-container">
<table class="arco-table-element" cellpadding="0" cellspacing="0">
<thead>
<tr class="arco-table-tr">
<th class="arco-table-th" v-for="(_, colIndex) in datas![0]" :key="colIndex">
<span class="arco-table-cell items-center justify-center">
<a-tooltip content="此列后新增列">
<a-button type="text" size="mini" @click="addColumn(colIndex)" class="delete-col-btn">
<template #icon>
<icon-plus />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="删除该列">
<a-button
type="text"
size="mini"
status="danger"
@click="deleteColumn(colIndex)"
:disabled="datas![0].length <= 1"
class="delete-col-btn"
>
<template #icon>
<icon-close />
</template>
</a-button>
</a-tooltip>
</span>
</th>
<th class="arco-table-th" :style="{ textAlign: 'center' }">
<span>操作</span>
</th>
</tr>
</thead>
<tbody>
<tr class="arco-table-tr" v-for="(row, rowIndex) in datas" :key="rowIndex">
<td class="arco-table-td" v-for="(col, colIndex) in row" :key="colIndex">
<span class="arco-table-cell">
<a-textarea auto-size v-model="datas![rowIndex][colIndex]" />
</span>
</td>
<td class="arco-table-td">
<span class="arco-table-cell items-center justify-center">
<a-tooltip content="此行后新增行">
<a-button type="text" size="mini" @click="addRow(rowIndex)" class="delete-col-btn">
<template #icon>
<icon-plus />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="删除该行">
<a-button size="mini" type="text" status="danger" @click="deleteRow(rowIndex)" :disabled="datas!.length <= 1">
<template #icon>
<icon-delete />
</template>
</a-button>
</a-tooltip>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup lang="ts">
// 该组件储存数据
const fontnote = defineModel<string>("fontnote")
const datas = defineModel<string[][]>()
// 行列操作
const deleteRow = (rowIndex: number) => {
datas.value!.splice(rowIndex, 1)
}
const deleteColumn = (colIndex: number) => {
datas.value!.forEach((row) => {
row.splice(colIndex, 1)
})
}
const addRow = (rowIndex: number) => {
const newRow = new Array(datas.value![0].length).fill("")
datas.value!.splice(rowIndex + 1, 0, newRow)
}
const addColumn = (colIndex: number) => {
// 处理空表格的特殊情况
if (datas.value!.length === 0) {
datas.value!.push([""])
return
}
// 新增后续列
datas.value!.forEach((row) => {
const insertPosition = colIndex === -1 ? row.length : colIndex + 1
row.splice(insertPosition, 0, "")
})
}
</script>
<style scoped lang="less">
.fontnote {
margin: 10px 0;
width: 100%;
}
</style>
.arco-textarea {
min-width: 120px;
}
.arco-table-cell {
padding: 5px;
}
</style>

View File

@@ -25,6 +25,10 @@ app.use(router)
app.use(pinia)
app.use(globalComponents)
// 全局loading插件
import loadingPlugin from "./plugins/loading"
app.use(loadingPlugin)
// 使用服务端请求数据管理库
import { VueQueryPlugin } from "@tanstack/vue-query"
app.use(VueQueryPlugin)

View File

@@ -0,0 +1,16 @@
import { createVNode, render } from "vue"
import LoadingComponent from "@/components/GlobalLoading/index.vue"
export default {
install(app) {
// 创建虚拟节点并渲染
const vnode = createVNode(LoadingComponent)
render(vnode, document.body)
// 挂载到全局属性
app.config.globalProperties.$loading = {
show: (text) => vnode.component?.exposed?.show(text),
hide: () => vnode.component?.exposed?.hide()
}
}
}