Ver código fonte

feat:添加农情互动列表页面

wangsisi 2 semanas atrás
pai
commit
66d4f6d7be

+ 263 - 0
src/components/popup/farmInfoPopup.vue

@@ -0,0 +1,263 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        round
+        class="farm-info-popup"
+        :close-on-click-overlay="true"
+        teleport="body"
+    >
+        <!-- 标题 -->
+        <div class="popup-title">为了更方便分析农场问题,请先完善您的农场信息</div>
+
+        <!-- 表单区域 -->
+        <el-form ref="formRef" :model="formData" :rules="rules" class="farm-form">
+            <!-- 农场位置 -->
+            <el-form-item label="农场位置" prop="location">
+                <el-input v-model="formData.location" placeholder="请输入农场位置" clearable />
+            </el-form-item>
+
+            <!-- 农场品种 -->
+            <el-form-item label="农场品种" prop="variety">
+                <div class="variety-select-wrap">
+                    <el-select
+                        v-model="formData.variety1"
+                        placeholder="请选择"
+                        class="variety-select"
+                        @change="handleVariety1Change"
+                    >
+                        <el-option
+                            v-for="item in variety1List"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        />
+                    </el-select>
+                    <el-select
+                        v-model="formData.variety2"
+                        placeholder="请选择"
+                        class="variety-select"
+                        :disabled="!formData.variety1"
+                        @change="handleVariety2Change"
+                    >
+                        <el-option
+                            v-for="item in variety2List"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        />
+                    </el-select>
+                </div>
+            </el-form-item>
+
+            <!-- 农场亩数 -->
+            <el-form-item label="农场亩数" prop="acreage">
+                <el-input v-model="formData.acreage" placeholder="请输入" type="number">
+                    <template #suffix>
+                        <span class="unit">亩</span>
+                    </template>
+                </el-input>
+            </el-form-item>
+
+            <!-- 农场名称 -->
+            <el-form-item label="农场名称" prop="farmName">
+                <el-input v-model="formData.farmName" placeholder="请输入农场名称" clearable />
+            </el-form-item>
+        </el-form>
+
+        <!-- 确认按钮 -->
+        <div class="btn-confirm" @click="handleConfirm">确认信息</div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from "vant";
+import { computed, ref, watch, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+
+const props = defineProps({
+    // 控制弹窗显示/隐藏
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    // 是否在点击遮罩层后关闭弹窗
+    closeOnClickOverlay: {
+        type: Boolean,
+        default: false,
+    },
+    // 初始表单数据
+    initialData: {
+        type: Object,
+        default: () => ({}),
+    },
+});
+
+const emit = defineEmits(["update:show", "confirm"]);
+
+// 处理v-model双向绑定
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit("update:show", value),
+});
+
+const formRef = ref(null);
+
+// 表单数据
+const formData = ref({
+    location: "广东省广州市从化区",
+    variety1: "荔枝",
+    variety2: "桂味",
+    acreage: "",
+    farmName: "",
+});
+
+// 品种选项(示例数据,实际应该从接口获取)
+const variety1List = ref([
+    { label: "荔枝", value: "荔枝" },
+    { label: "龙眼", value: "龙眼" },
+    { label: "芒果", value: "芒果" },
+    { label: "柑橘", value: "柑橘" },
+]);
+
+const variety2List = ref([
+    { label: "桂味", value: "桂味" },
+    { label: "妃子笑", value: "妃子笑" },
+    { label: "糯米糍", value: "糯米糍" },
+    { label: "黑叶", value: "黑叶" },
+]);
+
+// 自定义验证器:验证农场品种
+const validateVariety = (rule, value, callback) => {
+    if (!formData.value.variety1 || !formData.value.variety2) {
+        callback(new Error("请选择农场品种"));
+    } else {
+        callback();
+    }
+};
+
+// 表单验证规则
+const rules = {
+    location: [{ required: true, message: "请输入农场位置", trigger: "blur" }],
+    variety: [{ validator: validateVariety, trigger: "change" }],
+    acreage: [
+        { required: true, message: "请输入农场亩数", trigger: "blur" },
+        { pattern: /^\d+(\.\d+)?$/, message: "请输入有效的数字", trigger: "blur" },
+    ],
+    farmName: [{ required: true, message: "请输入农场名称", trigger: "blur" }],
+};
+
+// 监听初始数据变化
+watch(
+    () => props.initialData,
+    (newData) => {
+        if (newData && Object.keys(newData).length > 0) {
+            Object.assign(formData.value, newData);
+        }
+    },
+    { immediate: true, deep: true }
+);
+
+// 监听弹窗显示状态,重置表单
+watch(
+    () => props.show,
+    (newVal) => {
+        if (newVal) {
+            // 弹窗打开时,如果有初始数据则使用,否则使用默认值
+            if (props.initialData && Object.keys(props.initialData).length > 0) {
+                Object.assign(formData.value, props.initialData);
+            } else {
+                formData.value = {
+                    location: "广东省广州市从化区",
+                    variety1: "荔枝",
+                    variety2: "桂味",
+                    acreage: "",
+                    farmName: "",
+                };
+            }
+            // 清除验证状态
+            nextTick(() => {
+                formRef.value?.clearValidate();
+            });
+        }
+    }
+);
+
+// 品种1变化时,重置品种2并触发验证
+const handleVariety1Change = (val) => {
+    formData.value.variety2 = "";
+    // 触发品种验证
+    nextTick(() => {
+        formRef.value?.validateField("variety");
+    });
+    // 这里可以根据品种1的值动态加载品种2的选项
+    // 示例:不同品种对应不同的子品种列表
+};
+
+// 品种2变化时,触发验证
+const handleVariety2Change = () => {
+    nextTick(() => {
+        formRef.value?.validateField("variety");
+    });
+};
+
+// 确认信息
+const handleConfirm = async () => {
+    if (!formRef.value) return;
+
+    try {
+        await formRef.value.validate();
+        emit("confirm", { ...formData.value });
+        emit("update:show", false);
+    } catch (error) {
+        console.log("表单验证失败", error);
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.farm-info-popup {
+    width: 100%;
+    padding: 20px 16px;
+    border-radius: 12px;
+
+    .popup-title {
+        font-size: 16px;
+        color: #121212;
+    }
+
+    .farm-form {
+        margin: 16px 0;
+        background: rgba(33, 153, 248, 0.05);
+        padding: 10px;
+        border-radius: 5px;
+        border: 1px solid rgba(33, 153, 248, 0.2);
+        :deep(.el-form-item__label) {
+            color: #1d2129;
+        }
+        .variety-select-wrap {
+            display: flex;
+            gap: 10px;
+            width: 100%;
+
+            .variety-select {
+                flex: 1;
+            }
+        }
+
+        .unit {
+            color: #666;
+            font-size: 14px;
+            padding-right: 8px;
+        }
+    }
+
+    .btn-confirm {
+        padding: 10px;
+        background: #2199f8;
+        color: #ffffff;
+        border-radius: 4px;
+        font-size: 16px;
+        text-align: center;
+    }
+}
+</style>

+ 6 - 0
src/router/globalRoutes.js

@@ -426,4 +426,10 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/agri_record/subPages/statusDetail.vue"),
     },
+    // 农事互动列表
+    {
+        path: "/interaction_list",
+        name: "InteractionList",
+        component: () => import("@/views/old_mini/interactionList/index.vue"),
+    },
 ];

+ 61 - 60
src/views/old_mini/chat_frame/consult.vue

@@ -16,7 +16,6 @@
                                 'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png'
                             "
                         />
-                        <img src="" alt="" />
                         <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image' ,'card-bubble': msg.messageType === 'card'}">
                             <!-- 文本消息 -->
                             <div v-if="msg.messageType === 'text'" class="content">{{ msg.content }}</div>
@@ -45,18 +44,15 @@
                             </div>
 
                              <!-- 对话样式消息 -->
-                            <div v-if="msg.messageType === 'card'" class="card-message" @click="handleCardClick(msg)">
-                                <template v-if="(msg.cardType || msg.content.cardType) === 'quotation'">
-                                    <div class="card-title">向您发送了一张 服务报价单</div>
-                                    <img src="https://birdseye-img.sysuimars.com/temp/price.png" alt="" />
-                                </template>
-                                <template v-else>
-                                    <div class="card-title">{{ msg.title || msg.content.title }}</div>
-                                    <img :src="handleImgUrl(msg.coverUrl || msg.content.coverUrl)" alt="" />
-                                </template>
+                            <div v-if="msg.messageType === 'question'" class="question-message" @click="handleCardClick(msg)">
+                                <div class="question-title">{{ msg.content }}</div>
+                                <div class="image-wrap">
+                                    <img src="@/assets/img/monitor/image.png" alt="" />
+                                    <img src="@/assets/img/monitor/image.png" alt="" />
+                                    <img src="@/assets/img/monitor/image.png" alt="" />
+                                </div>
+                                <div class="btn-detail" @click="handleDetailClick">查看详情</div>
                             </div>
-
-                            <!-- <div class="time">{{ msg.time }}</div> -->
                         </div>
                     </template>
 
@@ -100,8 +96,6 @@
                                     <img :src="handleImgUrl(msg.coverUrl || msg.content.coverUrl)" alt="" />
                                 </template>
                             </div>
-
-                            <!-- <div class="time">{{ msg.time }}</div> -->
                         </div>
                         <!-- <div class="avatar avatar-r">{{ msg.senderName.charAt(0) }}</div> -->
                         <el-avatar
@@ -118,12 +112,12 @@
 
             <!-- 输入框区域 -->
             <div class="input-area">
-                <div class="toolbar">
+                <!-- <div class="toolbar">
                     <el-icon class="link" @click="startImageUpload"><Link /></el-icon>
                     <input type="file" ref="fileInput" accept="image/*" style="display: none" @change="handleImageUpload" />
-                </div>
+                </div> -->
 
-                <input type="text" v-model="inputMessage" placeholder="请输入你想说的话~" @keyup.enter="sendTextMessage" />
+                <input type="text" v-model="inputMessage" placeholder="给 专家 发送消息" @keyup.enter="sendTextMessage" />
                 <div class="send" @click="sendTextMessage">发送</div>
             </div>
 
@@ -136,7 +130,7 @@
 </template>
 
 <script setup>
-import { ref, onUnmounted, nextTick, watch, onActivated, onDeactivated } from "vue";
+import { ref, nextTick, onDeactivated, onMounted } from "vue";
 import { useRouter ,useRoute} from "vue-router";
 import { base_img_url2 } from "@/api/config";
 import { getFileExt } from "@/utils/util";
@@ -163,8 +157,6 @@ const props = defineProps({
     },
 });
 
-const emit = defineEmits(['update:name']);
-
 const curUserId = Number(localStorage.getItem("MINI_USER_ID"));
 const senderIcon = ref("");
 const receiverIcon = ref("");
@@ -182,12 +174,6 @@ const localUserInfoIcon = (() => {
 
 // 初始化本地头像为默认发送者头像
 senderIcon.value = localUserInfoIcon;
-const nameVal = ref('');
-
-// 监听 nameVal 变化,传递给父组件
-watch(nameVal, (newVal) => {
-    emit('update:name', newVal);
-});
 
 // mqtt 连接
 const mqttClient = ref(null);
@@ -199,13 +185,15 @@ const messages = ref([]);
 // 输入相关
 const inputMessage = ref("");
 const fileInput = ref(null);
-const showEmojiPicker = ref(false);
-const emojis = ["😀", "😂", "😍", "👍", "👋", "🎉", "❤️", "🙏"];
 
 function handleImageLoad() {
     scrollToBottom();
 }
 
+const handleDetailClick = () => {
+    router.push('/interaction_list');
+}
+
 // 图片预览
 const previewImage = ref(null);
 
@@ -256,7 +244,6 @@ const sendImageMessage = (thumbnailUrl) => {
         messageType: "image",
         senderIcon: senderIcon.value,
         content: thumbnailUrl,
-        time: getCurrentTime(),
     };
     sendMessage(message);
 };
@@ -319,21 +306,12 @@ const sendTextMessage = () => {
             messageType: "text",
             senderIcon: senderIcon.value,
             content: inputMessage.value,
-            time: getCurrentTime(),
         };
         sendMessage(message);
         inputMessage.value = "";
     }
 };
 
-// 辅助函数
-const getCurrentTime = () => {
-    return new Date().toLocaleTimeString("zh-CN", {
-        hour: "2-digit",
-        minute: "2-digit",
-    });
-};
-
 const scrollToBottom = () => {
     nextTick(() => {
         setTimeout(() => {
@@ -345,12 +323,10 @@ const scrollToBottom = () => {
 };
 
 const farmVal = ref("");
-const options = ref([]);
 const curRole = ref(null);
 
 // 点击农场报告对话框
 const handleReportClick = (msg) => {
-    console.log(msg);
     if(msg.reportType === 'farm_report' || msg.messageType === 'report'){
         const params = {
             farmId: msg.reportId || msg.content?.reportId,
@@ -362,6 +338,24 @@ const handleReportClick = (msg) => {
     }
 }
 
+// 页面加载时自动添加欢迎消息
+onMounted(() => {
+    const welcomeMessage = {
+        sender: "received",
+        messageType: "text",
+        content: "您好,我叫冼继东。我是种植专家,介绍专家介绍专家",
+        receiverIcon: receiverIcon.value || 'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png',
+    };
+    const questionMessage = {
+        sender: "received",
+        messageType: "question",
+        content: "为了更方便分析农场问题,请先采集农情互动信息",
+        receiverIcon: receiverIcon.value || 'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png',
+    };
+    messages.value.push(welcomeMessage,questionMessage);
+    scrollToBottom();
+});
+
 onDeactivated(() => {
     mqttClient.value && mqttClient.value.client.end(true);
 });
@@ -386,7 +380,7 @@ onDeactivated(() => {
     flex: 1;
     padding: 12px;
     overflow-y: auto;
-    background-color: #f5f5f5;
+    background-color: #fff;
     box-sizing: border-box;
     .message {
         display: flex;
@@ -395,7 +389,7 @@ onDeactivated(() => {
     .received {
         justify-content: flex-start;
         .bubble {
-            background-color: white;
+            background-color: #F4F5F8;
             border-radius: 0 10px 10px 10px;
             padding: 10px 12px;
             box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
@@ -434,14 +428,7 @@ onDeactivated(() => {
 
 .content {
     font-size: 16px;
-    line-height: 1.4;
-}
-
-.time {
-    font-size: 12px;
-    color: #fff;
-    margin-top: 5px;
-    text-align: right;
+    color: #666666;
 }
 
 .input-area {
@@ -586,17 +573,31 @@ onDeactivated(() => {
     }
 }
 
-.card-message{
-    .card-title{
-        font-size: 15px;
-        font-weight: 600;
-        color: #000;
-        margin-bottom: 5px;
+.question-message{
+    .question-title{
+        font-size: 16px;
+        color: #666666;
+        margin-bottom: 6px;
     }
-    img{
-        width: 222px;
-        height: 180px;
-        object-fit: cover;
+    .image-wrap{
+        display: flex;
+        // flex-wrap: wrap;
+        gap: 10px;
+        img{
+            flex: 1;
+            width: 75px;
+            height: 70px;
+            border-radius: 8px;
+            object-fit: cover;
+        }
+    }
+    .btn-detail{
+        font-size: 14px;
+        margin-top: 8px;
+        background: #FFFFFF;
+        border-radius: 6px;
+        text-align: center;
+        padding: 6px;
     }
 }
 

+ 6 - 7
src/views/old_mini/home/components/knowledgeCard.vue

@@ -2,7 +2,7 @@
     <div class="knowledge-card">
         <div class="knowledge-header">
             <div class="knowledge-title">
-                <div class="title-item" :class="{ active: activeKnowledgeId === 5 }" @click="handleKnowledgeClick(5)">
+                <div class="title-item" :class="{ active: activeKnowledgeId === 8 }" @click="handleKnowledgeClick(8)">
                     焦点话题
                 </div>
                 <div class="title-item" :class="{ active: activeKnowledgeId === 6 }" @click="handleKnowledgeClick(6)">
@@ -16,7 +16,7 @@
                 更多<el-icon class="more-icon"><ArrowRight /></el-icon>
             </div>
         </div>
-        <div class="knowledge-content focus-content" v-if="activeKnowledgeId === 5">
+        <div class="knowledge-content focus-content" v-if="activeKnowledgeId === 8">
             <div
                 class="focus-item"
                 @click="handleBannerClick(item.id)"
@@ -28,8 +28,8 @@
                     <div class="title van-ellipsis">{{ item.title }}</div>
                     <div class="focus-expert">
                         <div class="expert-name">
-                            <el-avatar :size="22" :src="item.expertIcon" />
-                            {{ item.expertName || '专家' }}
+                            <el-avatar :size="22" :src="item.icon" />
+                            {{ item.name || '专家' }}
                         </div>
                         <div class="date">{{ formatDate(item.createTime) }}</div>
                     </div>
@@ -37,8 +37,7 @@
                         <div class="answer-icon">
                         </div>
-                        <!-- {{ item.answer }} -->
-                        保证金通常是指在金融交易中,为了确保交易双方履行合约义务而支付的一定金额。保证金的组成可能因不同的交易类型和市场而有所不同
+                        {{ item.summary }}
                     </div>
                 </div>
             </div>
@@ -84,7 +83,7 @@ import { useStore } from "vuex";
 
 const router = useRouter();
 const store = useStore();
-const activeKnowledgeId = ref(5);
+const activeKnowledgeId = ref(8);
 
 // 按 topicId 缓存知识列表,避免每次切换都请求接口
 const knowledgeCache = ref({});

+ 304 - 0
src/views/old_mini/interactionList/index.vue

@@ -0,0 +1,304 @@
+<template>
+    <custom-header name="农情互动" bgColor="#f2f4f5"></custom-header>
+    <div class="interaction-list">
+        <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
+            <div
+                class="list-item"
+                v-for="(item, index) in listData"
+                :key="item.id || index"
+                :class="{ 'uploaded-item': item.upload }"
+            >
+                <!-- 标题区域 -->
+                <div class="item-header-wrapper" :class="{ 'has-status': item.upload }">
+                    <div class="item-header">长势采集: 物候进程</div>
+                    <div class="upload-status" v-show="item.upload">
+                        <el-icon class="status-icon"><SuccessFilled /></el-icon>
+                        <span class="status-text">上传成功</span>
+                    </div>
+                </div>
+
+                <!-- 展开状态内容 -->
+                <div class="expanded-content" v-show="item.upload && item.expanded">
+                    <!-- 原因说明 -->
+                    <div class="reason-text">原因原因: 便于记录农场档案,及时调整农事规划</div>
+
+                    <!-- 图片展示 -->
+                    <div class="uploaded-images">
+                        <img class="uploaded-img" src="@/assets/img/home/example-4.png" alt="" />
+                        <img class="uploaded-img" src="@/assets/img/home/example-4.png" alt="" />
+                        <img class="uploaded-img" src="@/assets/img/home/example-4.png" alt="" />
+                    </div>
+                </div>
+
+                <!-- 未上传状态内容 -->
+                <div v-show="!item.upload">
+                    <!-- 说明文字 -->
+                    <div class="item-desc">根据您上次农情互动根据您上次白点期</div>
+
+                    <upload exampleImg>
+                        <div class="upload-example">
+                            <img class="example" src="@/assets/img/home/example-4.png" alt="" />
+                            <img class="example" src="@/assets/img/home/plus.png" alt="" />
+                        </div>
+                    </upload>
+
+                    <!-- 输入框 -->
+                    <div class="input-wrapper">
+                        <el-input v-model="item.proportion" placeholder="当前果园达到物候期的比例" clearable />
+                    </div>
+
+                    <!-- 按钮区域 -->
+                    <div class="button-group">
+                        <div class="btn-not-reached" @click="handleNotReached(item)">暂未到达进程</div>
+                        <div class="btn-confirm" @click="handleConfirm(item)">确认上传</div>
+                    </div>
+                </div>
+
+                <!-- 比例信息(已上传状态显示) -->
+                <div class="proportion-info" v-show="item.upload">
+                    <span class="proportion-text">当前果园达到物候期的比例: {{ item.proportion || "10%" }}</span>
+                    <div class="toggle-btn" @click="toggleExpand(item)">
+                        <span>{{ item.expanded ? "收起" : "展开" }}</span>
+                        <el-icon :class="{ rotate: !item.expanded }"><CaretTop /></el-icon>
+                    </div>
+                </div>
+            </div>
+
+            <div class="empty-data" v-if="!loading && listData.length === 0">暂无数据</div>
+        </van-list>
+    </div>
+    <!-- 农场信息完善弹窗 -->
+    <farm-info-popup v-model:show="showFarmInfoPopup" @confirm="handleFarmInfoConfirm" />
+</template>
+<script setup>
+import { ref, onMounted } from "vue";
+import { List as VanList } from "vant";
+import { ElMessage } from "element-plus";
+import customHeader from "@/components/customHeader.vue";
+import FarmInfoPopup from "@/components/popup/farmInfoPopup.vue";
+import upload from "@/components/upload.vue";
+
+const showFarmInfoPopup = ref(false);
+const loading = ref(false);
+const finished = ref(false);
+const listData = ref([]);
+
+// 模拟数据,实际应该从接口获取
+const mockData = [
+    { id: 1, proportion: "", expanded: false },
+    { id: 2, proportion: "", expanded: false },
+    { id: 3, proportion: "10%", upload: true, expanded: false },
+];
+
+// 加载数据
+const onLoad = () => {
+    loading.value = true;
+    // 模拟接口请求
+    setTimeout(() => {
+        if (listData.value.length === 0) {
+            listData.value = [...mockData];
+        } else {
+            // 加载更多逻辑
+            finished.value = true;
+        }
+        loading.value = false;
+    }, 500);
+};
+
+// 暂未到达进程
+const handleNotReached = (item) => {
+    console.log("暂未到达进程", item);
+    ElMessage.info("已标记为暂未到达进程");
+    // 这里可以处理业务逻辑
+};
+
+// 确认上传
+const handleConfirm = (item) => {
+    if (!item.proportion) {
+        ElMessage.warning("请输入当前果园达到物候期的比例");
+        return;
+    }
+    console.log("确认上传", item);
+    ElMessage.success("上传成功");
+    // 标记为已上传状态
+    item.upload = true;
+    item.expanded = false;
+    // 这里可以处理提交逻辑
+};
+
+// 切换展开/收起
+const toggleExpand = (item) => {
+    item.expanded = !item.expanded;
+};
+
+// 确认农场信息
+const handleFarmInfoConfirm = (data) => {
+    console.log("确认的农场信息:", data);
+    // 这里可以处理提交逻辑
+};
+
+onMounted(() => {
+    // 初始化加载
+});
+</script>
+<style scoped lang="scss">
+.interaction-list {
+    width: 100%;
+    height: calc(100vh - 40px);
+    background: #f2f4f5;
+    padding: 12px;
+    box-sizing: border-box;
+    overflow-y: auto;
+
+    .list-item {
+        background: #ffffff;
+        border-radius: 6px;
+        padding: 10px;
+
+        .item-header-wrapper {
+            .item-header {
+                font-size: 16px;
+                color: #6f6f6f;
+                font-family: "PangMenZhengDao";
+                width: fit-content;
+                background: rgba(143, 143, 143, 0.1);
+                padding: 5px 10px;
+                border-radius: 2px;
+            }
+
+            .upload-status {
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                margin-left: 10px;
+
+                .status-icon {
+                    color: #2199f8;
+                    font-size: 17px;
+                }
+
+                .status-text {
+                    font-size: 14px;
+                    color: #2199f8;
+                }
+            }
+
+            &.has-status {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                .item-header {
+                    color: #2199f8;
+                    background: rgba(33, 153, 248, 0.1);
+                }
+            }
+        }
+
+        .expanded-content {
+            .reason-text {
+                padding: 12px 0;
+                color: #969696;
+                font-size: 14px;
+            }
+
+            .uploaded-images {
+                display: flex;
+                gap: 12px;
+                margin-bottom: 12px;
+
+                .uploaded-img {
+                    width: 80px;
+                    height: 80px;
+                    border-radius: 4px;
+                    object-fit: cover;
+                }
+            }
+        }
+
+        .item-desc {
+            padding: 12px 0;
+            color: #969696;
+        }
+
+        .input-wrapper {
+            margin: 12px 0;
+        }
+
+        .button-group {
+            display: flex;
+            gap: 12px;
+
+            .btn-not-reached,
+            .btn-confirm {
+                text-align: center;
+                border-radius: 4px;
+                padding: 6px;
+            }
+
+            .btn-not-reached {
+                width: 104px;
+                background: #fff;
+                color: #2199f8;
+                border: 1px solid rgba(33, 153, 248, 0.2);
+            }
+
+            .btn-confirm {
+                width: calc(100% - 104px - 12px);
+                background: #2199f8;
+                color: #ffffff;
+                border: 1px solid #2199f8;
+            }
+        }
+
+        .proportion-info {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-top: 12px;
+
+            .proportion-text {
+                color: #969696;
+                font-size: 14px;
+            }
+
+            .toggle-btn {
+                display: flex;
+                align-items: center;
+                gap: 2px;
+                color: #8D8D8D;
+                padding: 2px 10px;
+                border: 1px solid rgba(0, 0, 0, 0.2);
+                font-size: 12px;
+                border-radius: 25px;
+                background: #FFFFFF;
+                .rotate {
+                    transform: rotate(180deg);
+                }
+            }
+        }
+    }
+    .list-item + .list-item {
+        margin-top: 12px;
+    }
+
+    .empty-data {
+        text-align: center;
+        padding: 40px 0;
+        color: #999999;
+        font-size: 14px;
+    }
+
+    .upload-example {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .example {
+            width: 80px;
+            height: 80px;
+        }
+        .example + .example {
+            margin-left: 12px;
+        }
+    }
+}
+</style>