Jelajahi Sumber

feat:添加编辑方案弹窗

wangsisi 1 Minggu lalu
induk
melakukan
ae43ca3c4d
2 mengubah file dengan 425 tambahan dan 206 penghapusan
  1. 267 0
      src/views/planting/components/EditSchemeDialog.vue
  2. 158 206
      src/views/planting/index.vue

+ 267 - 0
src/views/planting/components/EditSchemeDialog.vue

@@ -0,0 +1,267 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="编辑方案"
+    width="560px"
+    class="edit-scheme-dialog"
+    :close-on-click-modal="false"
+    @closed="onClosed"
+  >
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="96px" class="edit-scheme-form">
+      <el-form-item label="方案名称:" prop="name">
+        <el-input v-model="form.name" placeholder="请输入方案名称" clearable />
+      </el-form-item>
+
+      <el-form-item label="适用作物:" required>
+        <div class="crop-selects">
+          <el-form-item prop="category" class="crop-selects__item">
+            <el-select v-model="form.category" placeholder="请选择品类" clearable>
+              <el-option
+                v-for="item in categoryOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="variety" class="crop-selects__item">
+            <el-select v-model="form.variety" placeholder="请选择品种" clearable>
+              <el-option
+                v-for="item in varietyOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="土壤类型:" prop="soilType">
+        <div class="field-block">
+          <el-radio-group v-model="form.soilType">
+            <el-radio
+              v-for="item in soilTypeOptions"
+              :key="item.value"
+              :label="item.value"
+            >
+              {{ item.label }}
+            </el-radio>
+          </el-radio-group>
+          <p class="field-hint">
+            遥感监测到某某农场的土壤类型为
+            <em class="field-hint__value">{{ soilTypeLabel }}</em>
+          </p>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="ph值:" prop="phValue">
+        <div class="field-block">
+          <el-radio-group v-model="form.phValue">
+            <el-radio
+              v-for="item in phOptions"
+              :key="item.value"
+              :label="item.value"
+            >
+              {{ item.label }}
+            </el-radio>
+          </el-radio-group>
+          <p class="field-hint">
+            遥感监测到某某农场的土壤ph值为
+            <em class="field-hint__value">{{ phValueLabel }}</em>
+          </p>
+        </div>
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="visible = false">取消</el-button>
+        <el-button type="warning" @click="onConfirm">确认</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from "vue";
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+const emit = defineEmits(["update:modelValue", "confirm"]);
+
+const visible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit("update:modelValue", val),
+});
+
+const formRef = ref(null);
+
+const categoryOptions = [
+  { label: "荔枝", value: "lychee" },
+  { label: "水稻", value: "rice" },
+];
+
+const varietyOptions = [
+  { label: "妃子笑", value: "feizixiao" },
+  { label: "桂味", value: "guiwei" },
+];
+
+const soilTypeOptions = [
+  { label: "沙土", value: "sand" },
+  { label: "壤土", value: "loam" },
+  { label: "粘土", value: "clay" },
+];
+
+const phOptions = [
+  { label: "强酸", value: "strong_acid" },
+  { label: "酸性", value: "acid" },
+  { label: "中性", value: "neutral" },
+  { label: "碱性", value: "alkaline" },
+  { label: "强碱", value: "strong_alkaline" },
+];
+
+const createDefaultForm = () => ({
+  name: "",
+  category: "",
+  variety: "",
+  soilType: "sand",
+  phValue: "strong_acid",
+});
+
+const form = reactive(createDefaultForm());
+
+const rules = {
+  name: [{ required: true, message: "请输入方案名称", trigger: "blur" }],
+  category: [{ required: true, message: "请选择品类", trigger: "change" }],
+  variety: [{ required: true, message: "请选择品种", trigger: "change" }],
+  soilType: [{ required: true, message: "请选择土壤类型", trigger: "change" }],
+  phValue: [{ required: true, message: "请选择ph值", trigger: "change" }],
+};
+
+const soilTypeLabel = computed(
+  () => soilTypeOptions.find((item) => item.value === form.soilType)?.label || "-"
+);
+
+const phValueLabel = computed(
+  () => phOptions.find((item) => item.value === form.phValue)?.label || "-"
+);
+
+const resetForm = () => {
+  Object.assign(form, createDefaultForm());
+  formRef.value?.clearValidate?.();
+};
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) resetForm();
+  }
+);
+
+const onClosed = () => {
+  resetForm();
+};
+
+const onConfirm = async () => {
+  if (!formRef.value) return;
+  try {
+    await formRef.value.validate();
+    emit("confirm", { ...form });
+    visible.value = false;
+  } catch {
+    // validation failed
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.edit-scheme-form {
+  padding: 8px 12px 0;
+
+  :deep(.el-form-item) {
+    margin-bottom: 22px;
+  }
+
+  :deep(.el-form-item__label) {
+    color: rgba(0, 0, 0, 0.65);
+  }
+
+  .crop-selects {
+    display: flex;
+    gap: 12px;
+    width: 100%;
+
+    &__item {
+      flex: 1;
+      margin-bottom: 0;
+
+      :deep(.el-form-item__content) {
+        margin-left: 0 !important;
+      }
+
+      .el-select {
+        width: 100%;
+      }
+    }
+  }
+
+  .field-block {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .field-hint {
+    margin: 0;
+    font-size: 13px;
+    line-height: 1.5;
+    color: rgba(0, 0, 0, 0.45);
+
+    &__value {
+      color: $warning-color;
+      font-style: normal;
+    }
+  }
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+}
+</style>
+
+<style lang="scss">
+.edit-scheme-dialog {
+  .el-dialog__header {
+    margin-right: 0;
+    padding: 16px 20px;
+    border-bottom: 1px solid #e7e7e7;
+  }
+
+  .el-dialog__title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #303133;
+  }
+
+  .el-dialog__body {
+    padding: 20px 24px 8px;
+  }
+
+  .el-dialog__footer {
+    padding: 16px 20px 20px;
+    border-top: 1px solid #e7e7e7;
+  }
+
+  .el-radio {
+    --el-radio-text-color: rgba(0, 0, 0, 0.65);
+  }
+}
+</style>

+ 158 - 206
src/views/planting/index.vue

@@ -17,7 +17,7 @@
       <!-- 筛选区 -->
       <div class="filter-panel">
         <div class="filter-row">
-          <span class="filter-label">区域:</span>
+          <span class="filter-label">区域</span>
           <div class="filter-content region-selects">
             <el-select v-model="filters.province" placeholder="选择省份" clearable class="region-select">
               <el-option v-for="item in provinceOptions" :key="item.value" :label="item.label" :value="item.value" />
@@ -29,22 +29,22 @@
         </div>
 
         <div class="filter-row">
-          <span class="filter-label">品类:</span>
+          <span class="filter-label">品类</span>
           <div class="filter-content tag-list">
-            <button v-for="(item, idx) in categoryOptions" :key="`category-${idx}`" type="button" class="filter-tag"
+            <div v-for="(item, idx) in categoryOptions" :key="`category-${idx}`" type="button" class="filter-tag"
               :class="{ 'is-active': filters.categoryIndex === idx }" @click="filters.categoryIndex = idx">
               {{ item }}
-            </button>
+            </div>
           </div>
         </div>
 
         <div class="filter-row">
-          <span class="filter-label">品种:</span>
+          <span class="filter-label">品种</span>
           <div class="filter-content tag-list">
-            <button v-for="(item, idx) in varietyOptions" :key="`variety-${idx}`" type="button" class="filter-tag"
+            <div v-for="(item, idx) in varietyOptions" :key="`variety-${idx}`" type="button" class="filter-tag"
               :class="{ 'is-active': filters.varietyIndex === idx }" @click="filters.varietyIndex = idx">
               {{ item }}
-            </button>
+            </div>
           </div>
         </div>
       </div>
@@ -53,27 +53,16 @@
       <div class="table-wrap">
         <el-table :data="tableData" border class="scheme-table" :header-cell-style="headerCellStyle"
           :cell-style="{ verticalAlign: 'top' }">
-          <el-table-column prop="name" label="农事名称" min-width="110" fixed>
+          <el-table-column prop="name" label="农事名称" min-width="110" fixed />
+          <el-table-column label="农事类型" min-width="120">
             <template #default="{ row }">
-              <span class="cell-name">{{ row.name }}</span>
-            </template>
-          </el-table-column>
-
-          <el-table-column label="农事类型" min-width="200">
-            <template #default="{ row }">
-              <div class="link-group">
-                <a v-for="(item, idx) in row.types" :key="idx" class="cell-link" href="javascript:;">{{ item }}</a>
+              <div class="type-tags">
+                <span v-for="(item, idx) in row.types" :key="idx" class="type-tag">{{ item }}</span>
               </div>
             </template>
           </el-table-column>
-
-          <el-table-column prop="trigger" label="触发条件" min-width="160">
-            <template #default="{ row }">
-              <p class="cell-text">{{ row.trigger }}</p>
-            </template>
-          </el-table-column>
-
-          <el-table-column label="药肥处方" min-width="280">
+          <el-table-column prop="trigger" label="触发条件" min-width="160" />
+          <el-table-column label="药肥处方" min-width="260">
             <template #default="{ row }">
               <div class="prescription">
                 <div class="prescription__title">{{ row.prescription.category }}</div>
@@ -83,39 +72,26 @@
               </div>
             </template>
           </el-table-column>
-
           <el-table-column label="执行规程" align="center">
-            <el-table-column label="执行窗口" min-width="140">
-              <template #default="{ row }">
-                <p class="cell-text">{{ row.execute.window }}</p>
-              </template>
-            </el-table-column>
+            <el-table-column label="执行窗口" min-width="140" prop="execute.window" />
             <el-table-column label="执行设备" min-width="120">
               <template #default="{ row }">
-                <div class="link-group link-group--col">
-                  <a v-for="(item, idx) in row.execute.devices" :key="idx" class="cell-link" href="javascript:;">{{ item
-                    }}</a>
+                <div class="type-tags">
+                  <span v-for="(item, idx) in row.execute.devices" :key="idx" class="type-tag">{{ item }}</span>
                 </div>
               </template>
             </el-table-column>
-            <el-table-column label="注意事项" min-width="160">
-              <template #default="{ row }">
-                <p class="cell-text">{{ row.execute.notes }}</p>
-              </template>
-            </el-table-column>
+            <el-table-column label="注意事项" min-width="160" prop="execute.notes" />
           </el-table-column>
-
           <el-table-column label="复核规程" align="center">
-            <el-table-column label="复核时间" min-width="110">
-              <template #default="{ row }">
-                <p class="cell-text">{{ row.review.time }}</p>
-              </template>
-            </el-table-column>
-            <el-table-column label="复核标准" min-width="160">
+            <el-table-column label="复核时间" min-width="120">
               <template #default="{ row }">
-                <p class="cell-text">{{ row.review.standard }}</p>
+                <span class="review-time">
+                  执行后 <em class="review-time__num">{{ row.review.days }}</em> 天
+                </span>
               </template>
             </el-table-column>
+            <el-table-column label="复核标准" prop="review.standard" min-width="160" />
           </el-table-column>
         </el-table>
       </div>
@@ -127,18 +103,22 @@
         </el-button>
       </div>
     </div>
+
+    <EditSchemeDialog v-model="editDialogVisible" @confirm="onEditConfirm" />
   </div>
 </template>
 
 <script setup>
 import { reactive, ref } from "vue";
 import { ElMessage } from "element-plus";
+import EditSchemeDialog from "./components/EditSchemeDialog.vue";
 
 const tabs = [
   { label: "种植方案", value: "scheme" },
   { label: "我的方案", value: "mine" },
 ];
 const activeTab = ref("scheme");
+const editDialogVisible = ref(false);
 
 const filters = reactive({
   province: "",
@@ -184,7 +164,7 @@ const tableData = ref([
       notes: placeholderText,
     },
     review: {
-      time: "执行后 7 天",
+      days: 7,
       standard: placeholderText,
     },
   },
@@ -206,7 +186,7 @@ const tableData = ref([
       notes: placeholderText,
     },
     review: {
-      time: "执行后 7 天",
+      days: 7,
       standard: placeholderText,
     },
   },
@@ -228,17 +208,16 @@ const tableData = ref([
       notes: placeholderText,
     },
     review: {
-      time: "执行后 7 天",
+      days: 7,
       standard: placeholderText,
     },
   },
 ]);
 
 const headerCellStyle = {
-  background: "#f5f7fa",
-  color: "#606266",
-  fontWeight: 500,
-  textAlign: "center",
+  background: "#F5F5F5",
+  color: "#535353",
+  fontWeight: 400,
 };
 
 const onAddDevice = () => {
@@ -246,6 +225,10 @@ const onAddDevice = () => {
 };
 
 const onCopyScheme = () => {
+  editDialogVisible.value = true;
+};
+
+const onEditConfirm = () => {
   ElMessage.success("已复制为我的方案");
 };
 </script>
@@ -253,19 +236,24 @@ const onCopyScheme = () => {
 <style lang="scss" scoped>
 $primary: $warning-color;
 $primary-light: color.mix(#fff, $warning-color, 90%);
-$link: #409eff;
-$text: #303133;
-$text-secondary: rgba(0, 0, 0, 0.6);
+$text: rgba(0, 0, 0, 0.6);
 $border: #E7E7E7;
 
 .planting-page {
   box-sizing: border-box;
   min-height: 100%;
+  height: 100%;
   padding: 25px;
+  display: flex;
+  flex-direction: column;
 
   .page-card {
+    flex: 1;
+    min-height: 0;
     background: #fff;
     padding: 10px;
+    display: flex;
+    flex-direction: column;
 
     .page-header {
       display: flex;
@@ -279,8 +267,8 @@ $border: #E7E7E7;
 
         &__item {
           position: relative;
-          padding: 5px 8px;
-          color: $text-secondary;
+          padding: 13px 16px;
+          color: $text;
           cursor: pointer;
 
           &:hover {
@@ -295,7 +283,7 @@ $border: #E7E7E7;
               position: absolute;
               left: 0;
               right: 0;
-              bottom: 0;
+              bottom: -2px;
               height: 2px;
               background: $primary;
               border-radius: 2px 2px 0 0;
@@ -303,167 +291,131 @@ $border: #E7E7E7;
           }
         }
       }
-    }
-  }
-}
-
-
-
-.btn-add {
-  height: 32px;
-  padding: 0 16px;
-  font-size: 13px;
-}
-
-.filter-panel {
-  padding: 20px 0 8px;
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-.filter-row {
-  display: flex;
-  align-items: flex-start;
-  gap: 12px;
-}
-
-.filter-label {
-  flex-shrink: 0;
-  width: 42px;
-  line-height: 32px;
-  font-size: 14px;
-  color: $text;
-  text-align: right;
-}
-
-.filter-content {
-  flex: 1;
-  min-width: 0;
-}
-
-.region-selects {
-  display: flex;
-  gap: 12px;
-}
-
-.region-select {
-  width: 160px;
-}
-
-.tag-list {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-}
-
-.filter-tag {
-  appearance: none;
-  border: 1px solid #dcdfe6;
-  background: #f5f7fa;
-  color: $text-secondary;
-  height: 32px;
-  padding: 0 16px;
-  border-radius: 4px;
-  font-size: 13px;
-  line-height: 30px;
-  cursor: pointer;
-  transition: all 0.2s;
-
-  &:hover {
-    border-color: $primary;
-    color: $primary;
-  }
 
-  &.is-active {
-    background: $primary-light;
-    border-color: $primary;
-    color: $primary;
-  }
-}
-
-.table-wrap {
-  flex: 1;
-  min-height: 0;
-  overflow: auto;
-}
+      .btn-add {
+        height: 32px;
+        padding: 0 16px;
+      }
+    }
 
-.scheme-table {
-  width: 100%;
+    .filter-panel {
+      padding: 20px 10px;
+      display: flex;
+      flex-direction: column;
+      gap: 10px;
 
-  :deep(.el-table__header th) {
-    font-size: 13px;
-  }
+      .filter-row {
+        display: flex;
+        gap: 8px;
 
-  :deep(.el-table__body td) {
-    padding: 14px 0;
-  }
+        .filter-label {
+          line-height: 32px;
+          color: $text;
+        }
 
-  :deep(.el-table__cell) {
-    vertical-align: top;
-  }
-}
+        .filter-content {
+          &.region-selects {
+            display: flex;
+            gap: 8px;
 
-.cell-name {
-  font-weight: 600;
-  color: $text;
-  font-size: 14px;
-}
+            .region-select {
+              width: 160px;
+            }
+          }
 
-.cell-text {
-  margin: 0;
-  font-size: 13px;
-  line-height: 1.6;
-  color: $text-secondary;
-  word-break: break-all;
-}
+          &.tag-list {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+
+            .filter-tag {
+              border: 1px solid transparent;
+              background: #F3F3F3;
+              color: $text;
+              padding: 5px 10px;
+              border-radius: 3px;
+              cursor: pointer;
+
+              &:hover {
+                border-color: $primary;
+                background: $primary-light;
+                color: $primary;
+              }
+
+              &.is-active {
+                background: $primary-light;
+                border-color: $primary;
+                color: $primary;
+              }
+            }
+          }
+        }
+      }
+    }
 
-.link-group {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px 12px;
+    .table-wrap {
+      flex: 1;
+      min-height: 0;
+      overflow: auto;
+
+      .scheme-table {
+        color: #000;
+
+        .type-tags {
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+          gap: 4px;
+
+          .type-tag {
+            padding: 1px 6px;
+            border-radius: 4px;
+            font-size: 12px;
+            color: #2199F8;
+            background: rgba(33, 153, 248, 0.1);
+          }
+        }
 
-  &--col {
-    flex-direction: column;
-    gap: 6px;
-  }
-}
+        .prescription {
+          &__title {
+            display: inline-block;
+            width: fit-content;
+            color: #4C4C4C;
+            margin-bottom: 2px;
+            background: rgba(125, 125, 125, 0.1);
+            padding: 0 6px;
+            border-radius: 2px;
+          }
 
-.cell-link {
-  color: $link;
-  font-size: 13px;
-  text-decoration: none;
-  line-height: 1.5;
+          &__line {
+            font-size: 13px;
+          }
+        }
 
-  &:hover {
-    text-decoration: underline;
-  }
-}
+        .review-time {
+          color: #000;
+          font-style: normal;
 
-.prescription {
-  font-size: 13px;
-  line-height: 1.7;
-  color: $text-secondary;
+          &__num {
+            color: $primary;
+            font-style: normal;
+            font-weight: 500;
+          }
+        }
+      }
+    }
 
-  &__title {
-    color: $text;
-    margin-bottom: 2px;
-  }
+    .page-footer {
+      flex-shrink: 0;
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+      padding: 20px 10px 10px;
 
-  &__line {
-    word-break: break-all;
+      .btn-copy {
+        padding: 10px 16px;
+      }
+    }
   }
 }
-
-.page-footer {
-  display: flex;
-  justify-content: flex-end;
-  padding-top: 20px;
-}
-
-.btn-copy {
-  height: 36px;
-  padding: 0 20px;
-  font-size: 14px;
-}
 </style>