增加创建和更新计划基本信息的界面
This commit is contained in:
21
frontend/dist/assets/index.48fb8fe6.js
vendored
21
frontend/dist/assets/index.48fb8fe6.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
frontend/dist/assets/index.cb9d3828.js
vendored
Normal file
21
frontend/dist/assets/index.cb9d3828.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>猪场管理系统</title>
|
<title>猪场管理系统</title>
|
||||||
<script type="module" crossorigin src="/assets/index.48fb8fe6.js"></script>
|
<script type="module" crossorigin src="/assets/index.cb9d3828.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index.2ad61d14.css">
|
<link rel="stylesheet" href="/assets/index.bcc76856.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
303
frontend/src/components/FeedPlanForm.vue
Normal file
303
frontend/src/components/FeedPlanForm.vue
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
<template>
|
||||||
|
<div class="feed-plan-form">
|
||||||
|
<div class="form-header">
|
||||||
|
<h2>{{ isEditMode ? '编辑饲喂计划' : '新建饲喂计划' }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">计划名称 *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
v-model="form.name"
|
||||||
|
required
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">计划描述</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
v-model="form.description"
|
||||||
|
rows="3"
|
||||||
|
:disabled="loading"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>计划类型 *</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label class="radio-item">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
v-model="form.type"
|
||||||
|
value="manual"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
手动触发
|
||||||
|
</label>
|
||||||
|
<label class="radio-item">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
v-model="form.type"
|
||||||
|
value="auto"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
自动触发
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="form.enabled"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
启用计划
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="form.type === 'auto'" class="form-group">
|
||||||
|
<label for="schedule_cron">定时表达式</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="schedule_cron"
|
||||||
|
v-model="form.schedule_cron"
|
||||||
|
placeholder="例如: 0 0 7 * * *"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
<div class="help-text">Cron表达式,用于设置自动执行时间</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="execution_limit">执行次数限制</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="execution_limit"
|
||||||
|
v-model.number="form.execution_limit"
|
||||||
|
min="0"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
<div class="help-text">0表示无限制</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
@click="$emit('cancel')"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
:disabled="loading || isSubmitting"
|
||||||
|
>
|
||||||
|
{{ loading || isSubmitting ? '处理中...' : (isEditMode ? '更新计划' : '创建计划') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FeedPlanForm',
|
||||||
|
props: {
|
||||||
|
// 编辑模式下的初始数据
|
||||||
|
initialData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
type: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
schedule_cron: '',
|
||||||
|
execution_limit: 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 是否为编辑模式
|
||||||
|
isEditMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 提交时的加载状态
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: { ...this.initialData },
|
||||||
|
isSubmitting: false // 防止重复提交
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
// 监听初始数据变化,更新表单
|
||||||
|
initialData: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.form = { ...newVal }
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async handleSubmit() {
|
||||||
|
// 防止重复提交
|
||||||
|
if (this.isSubmitting || this.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 表单验证
|
||||||
|
if (!this.form.name.trim()) {
|
||||||
|
alert('请输入计划名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.type === 'auto' && this.form.schedule_cron && !this.isValidCron(this.form.schedule_cron)) {
|
||||||
|
alert('请输入有效的Cron表达式')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发提交事件
|
||||||
|
this.$emit('submit', { ...this.form })
|
||||||
|
} finally {
|
||||||
|
// 在下一个tick重置提交状态,确保事件已经触发
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.isSubmitting = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 简单的Cron表达式验证
|
||||||
|
isValidCron(cron) {
|
||||||
|
// 这里可以添加更复杂的验证逻辑
|
||||||
|
// 现在只是简单检查格式
|
||||||
|
return typeof cron === 'string' && cron.trim().length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.feed-plan-form {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group input[type="number"],
|
||||||
|
.form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"]:focus,
|
||||||
|
.form-group input[type="number"]:focus,
|
||||||
|
.form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007bff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:disabled,
|
||||||
|
.form-group textarea:disabled {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background-color: #0069d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -60,17 +60,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- 计划表单模态框 -->
|
||||||
|
<div v-if="showModal" class="modal-overlay" @click="closeModal">
|
||||||
|
<div class="modal-content" @click.stop>
|
||||||
|
<FeedPlanForm
|
||||||
|
:initial-data="currentPlan"
|
||||||
|
:is-edit-mode="modalType === 'edit'"
|
||||||
|
:loading="submitting"
|
||||||
|
@submit="submitForm"
|
||||||
|
@cancel="closeModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import FeedPlanForm from '../components/FeedPlanForm.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FeedPlan',
|
name: 'FeedPlan',
|
||||||
|
components: {
|
||||||
|
FeedPlanForm
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: '',
|
username: '',
|
||||||
plans: [],
|
plans: [],
|
||||||
loading: true
|
loading: true,
|
||||||
|
// 控制模态框显示
|
||||||
|
showModal: false,
|
||||||
|
// 当前操作类型: 'create' 或 'edit'
|
||||||
|
modalType: 'create',
|
||||||
|
// 当前编辑的计划
|
||||||
|
currentPlan: null,
|
||||||
|
// 提交时的加载状态
|
||||||
|
submitting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -113,14 +139,75 @@ export default {
|
|||||||
|
|
||||||
// 创建计划
|
// 创建计划
|
||||||
createPlan() {
|
createPlan() {
|
||||||
// TODO: 实现创建计划逻辑
|
this.modalType = 'create'
|
||||||
alert('创建计划功能待实现')
|
this.currentPlan = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
type: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
schedule_cron: '',
|
||||||
|
execution_limit: 0
|
||||||
|
}
|
||||||
|
this.showModal = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// 编辑计划
|
// 编辑计划
|
||||||
editPlan(plan) {
|
editPlan(plan) {
|
||||||
// TODO: 实现编辑计划逻辑
|
this.modalType = 'edit'
|
||||||
alert(`编辑计划: ${plan.name}`)
|
// 深拷贝计划数据,避免直接修改原数据
|
||||||
|
this.currentPlan = JSON.parse(JSON.stringify(plan))
|
||||||
|
this.showModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关闭模态框
|
||||||
|
closeModal() {
|
||||||
|
this.showModal = false
|
||||||
|
this.currentPlan = null
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
async submitForm(formData) {
|
||||||
|
// 防止重复提交
|
||||||
|
if (this.submitting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitting = true
|
||||||
|
try {
|
||||||
|
let url, method
|
||||||
|
if (this.modalType === 'create') {
|
||||||
|
url = '/api/v1/feed/plan/create'
|
||||||
|
method = 'POST'
|
||||||
|
} else {
|
||||||
|
url = '/api/v1/feed/plan/update'
|
||||||
|
method = 'POST'
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (response.ok && data.code === 0) {
|
||||||
|
// 提交成功,关闭模态框并重新加载列表
|
||||||
|
this.closeModal()
|
||||||
|
await this.loadPlans()
|
||||||
|
alert(this.modalType === 'create' ? '创建计划成功' : '更新计划成功')
|
||||||
|
} else {
|
||||||
|
alert((this.modalType === 'create' ? '创建计划失败: ' : '更新计划失败: ') + (data.message || '未知错误'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(this.modalType === 'create' ? '创建计划失败:' : '更新计划失败:', error)
|
||||||
|
alert((this.modalType === 'create' ? '创建计划失败: ' : '更新计划失败: ') + error.message)
|
||||||
|
} finally {
|
||||||
|
this.submitting = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除计划
|
// 删除计划
|
||||||
@@ -400,4 +487,28 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 模态框样式 */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src')
|
||||||
|
}
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ func (c *Controller) convertToCreateModel(req *CreateRequest) *model.FeedingPlan
|
|||||||
ExecutionLimit: req.ExecutionLimit,
|
ExecutionLimit: req.ExecutionLimit,
|
||||||
ParentID: req.ParentID,
|
ParentID: req.ParentID,
|
||||||
OrderInParent: req.OrderInParent,
|
OrderInParent: req.OrderInParent,
|
||||||
|
// 不需要显式设置ID字段,仓库层会处理
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换步骤
|
// 转换步骤
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ func (f *feedPlanRepo) FindFeedingPlanByID(feedingPlanID uint) (*model.FeedingPl
|
|||||||
|
|
||||||
// CreateFeedingPlan 创建饲料计划,包括步骤和子计划
|
// CreateFeedingPlan 创建饲料计划,包括步骤和子计划
|
||||||
func (f *feedPlanRepo) CreateFeedingPlan(feedingPlan *model.FeedingPlan) error {
|
func (f *feedPlanRepo) CreateFeedingPlan(feedingPlan *model.FeedingPlan) error {
|
||||||
|
// 清空所有ID,确保创建新记录
|
||||||
|
f.clearAllIDs(feedingPlan)
|
||||||
|
|
||||||
return f.db.Transaction(func(tx *gorm.DB) error {
|
return f.db.Transaction(func(tx *gorm.DB) error {
|
||||||
return f.createFeedingPlanWithTx(tx, feedingPlan)
|
return f.createFeedingPlanWithTx(tx, feedingPlan)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user