wenningning 2 veckor sedan
förälder
incheckning
6fde4deeb1
45 ändrade filer med 3757 tillägg och 496 borttagningar
  1. 93 0
      manager-admin/src/api/pjConfigOptions.js
  2. 1 1
      manager-admin/src/components/GoodsSkuPicker/src/mixin.js
  3. 2 1
      manager-admin/src/utils/request.js
  4. 99 1
      manager-admin/src/views/goods/goodsDetail.vue
  5. 100 2
      manager-admin/src/views/goods/goodsEdit.vue
  6. 10 3
      manager-admin/src/views/goods/goodsList.vue
  7. 2 2
      manager-admin/src/views/goods/goodsListView.vue
  8. 50 8
      manager-admin/src/views/goods/goodsSelect.vue
  9. 500 0
      manager-admin/src/views/goodsItem/itemList.vue
  10. 2 2
      manager-admin/src/views/member/member-manage/memberList.vue
  11. 2 2
      manager-admin/src/views/member/member-manage/memberListAddress.vue
  12. 2 2
      manager-admin/src/views/member/member-manage/memberListAsk.vue
  13. 2 2
      manager-admin/src/views/member/member-manage/memberListComments.vue
  14. 2 2
      manager-admin/src/views/member/member-manage/memberListOrder.vue
  15. 2 2
      manager-admin/src/views/member/member-manage/memberListPoints.vue
  16. 2 2
      manager-admin/src/views/member/member-manage/memberRecycle.vue
  17. 2 2
      manager-admin/src/views/member/pre-deposit/preDepositDetail.vue
  18. 2 2
      manager-admin/src/views/member/pre-deposit/rechargeList.vue
  19. 2 2
      manager-admin/src/views/order/collectionList.vue
  20. 2 2
      manager-admin/src/views/order/complaint/complaintList.vue
  21. 2 2
      manager-admin/src/views/order/complaint/subjectList.vue
  22. 2 2
      manager-admin/src/views/order/orderList.vue
  23. 2 2
      manager-admin/src/views/order/receiptExamine.vue
  24. 2 2
      manager-admin/src/views/order/receiptHistory.vue
  25. 2 2
      manager-admin/src/views/order/refundList.vue
  26. 2 2
      manager-admin/src/views/order/serviceList.vue
  27. 371 15
      manager-admin/src/views/pjDraft/FsGoods.vue
  28. 436 0
      manager-admin/src/views/pjDraft/FsGoods2.vue
  29. 2 2
      manager-admin/src/views/pjDraft/List.vue
  30. 2 2
      manager-admin/src/views/pjDraft/SelfList.vue
  31. 4 4
      manager-admin/src/views/pjOrder/Items.vue
  32. 2 2
      manager-admin/src/views/pjOrder/List.vue
  33. 652 0
      manager-admin/src/views/pjOrder/OrderItems copy.vue
  34. 1375 397
      manager-admin/src/views/pjOrder/OrderItems.vue
  35. 2 2
      manager-admin/src/views/pjOrder/Receipt.vue
  36. 2 2
      manager-admin/src/views/pjOrder/SelfList.vue
  37. 2 2
      manager-admin/src/views/setting/auth-settings/administratorManage.vue
  38. 2 2
      manager-admin/src/views/setting/auth-settings/roleManage.vue
  39. 2 2
      manager-admin/src/views/setting/search-settings/searchHistory.vue
  40. 2 2
      manager-admin/src/views/setting/search-settings/searchKeyword.vue
  41. 2 2
      manager-admin/src/views/setting/search-settings/searchTips.vue
  42. 2 2
      manager-admin/src/views/shop/settlement-manage/settlement.vue
  43. 2 2
      manager-admin/src/views/shop/settlement-manage/settlementList.vue
  44. 2 2
      manager-admin/src/views/shop/shop-manage/shopAudit.vue
  45. 2 2
      manager-admin/src/views/shop/shop-manage/shopList.vue

+ 93 - 0
manager-admin/src/api/pjConfigOptions.js

@@ -0,0 +1,93 @@
+/**
+ * 产品配置项API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 获取产品配置项列表
+ * @param params
+ */
+export function getList(params) {
+  return request({
+    url: 'pjConfigOptions/page',
+    method: 'post',
+    loading: true,
+    data: params
+  })
+}
+
+export function addModel(params) {
+  return request({
+    url: 'pjConfigOptions',
+    method: 'post',
+    loading: true,
+    data: params
+  })
+}
+
+export function editModel(params) {
+  return request({
+    url: `pjConfigOptions/${params.id}`,
+    method: 'put',
+    loading: true,
+    data: params
+  })
+}
+
+export function update(params) {
+  return request({
+    url: `pjConfigOptions/update`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    loading: true,
+    data: params
+  })
+}
+
+export function deletes(ids) {
+  return request({
+    url: `pjConfigOptions/${ids}`,
+    method: 'delete',
+    loading: true
+  })
+}
+
+export function addOptionValues(params) {
+  return request({
+    url: `pjConfigOptionValues`,
+    method: 'post'
+  })
+}
+
+export function editOptionValues(params) {
+  return request({
+    url: `pjConfigOptionValues/${params.id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+export function deletesOptionValues(ids) {
+  return request({
+    url: `pjConfigOptionValues/${ids}`,
+    method: 'delete'
+  })
+}
+
+// 通过商品id获取其相关配置项
+export function getByProductId(productId) {
+  return request({
+    url: 'pjConfigOptions/get_by_product_id/' + productId,
+    method: 'get'
+  })
+}
+
+export function getByProductIds(productIds) {
+  return request({
+    url: 'pjConfigOptions/get_by_products_ids',
+    headers: { 'Content-Type': 'application/json' },
+    method: 'post',
+    data: productIds
+  })
+}

+ 1 - 1
manager-admin/src/components/GoodsSkuPicker/src/mixin.js

@@ -62,7 +62,7 @@ export default {
     return {
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         // 商品分类路径
         category_path: '',
         // 店铺名称

+ 2 - 1
manager-admin/src/utils/request.js

@@ -15,7 +15,7 @@ const qs = require('qs')
 // 创建axios实例
 const service = axios.create({
   baseURL: api.admin,
-  timeout: 5000,
+  timeout: 15000,
   httpsAgent: new https.Agent({
     rejectUnauthorized: false
   }),
@@ -29,6 +29,7 @@ service.interceptors.request.use(config => {
   const is_json = config.headers['Content-Type'] === 'application/json'
   if (is_put_post && is_json) {
     config.data = JSON.stringify(config.data)
+    console.log('我是json提交的')
   }
   if (is_put_post && !is_json) {
     config.data = qs.stringify(config.data, { arrayFormat: 'repeat' })

+ 99 - 1
manager-admin/src/views/goods/goodsDetail.vue

@@ -84,6 +84,32 @@
             <span v-html="goodsForm.description"></span>
           </el-card>
         </el-form-item>
+
+        <!-- 添加配置项展示区域 -->
+        <el-form-item label="产品配置项:" class="config-options">
+          <div v-if="configOptions.length > 0">
+            <div v-for="option in configOptions" :key="option.id" class="config-option-item">
+              <div class="option-header">
+                <span class="option-name">{{ option.name }}</span>
+                <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+              </div>
+              <div class="option-values" v-if="option.option_values && option.option_values.length > 0">
+                <el-tag
+                  v-for="value in option.option_values"
+                  :key="value.id"
+                  size="small"
+                  :type="value.status === 1 ? 'success' : 'info'"
+                  style="margin-right: 5px; margin-bottom: 5px;"
+                >
+                  {{ value.value }}
+                </el-tag>
+              </div>
+            </div>
+          </div>
+          <div v-else class="no-config">
+            暂无配置项
+          </div>
+        </el-form-item>
       </el-form>
     </div>
   </div>
@@ -91,6 +117,7 @@
 
 <script>
 import * as API_goods from '@/api/goods'
+import * as API_configOptions from '@/api/pjConfigOptions'
 import { RegExp } from '~/ui-utils'
 
 export default {
@@ -118,7 +145,8 @@ export default {
       videoErrorTip: false,
 
       /** 商品详情的校验规则 */
-      goodsFormRule: {}
+      goodsFormRule: {},
+      configOptions: [] // 添加配置项数据
     }
   },
   mounted() {
@@ -140,6 +168,8 @@ export default {
           if (res.tags) {
             this.goodsForm.tags = this.goodsForm.tags.split(',')
           }
+          // 获取配置项数据
+          this.loadConfigOptions()
         })
       } else {
         API_goods.getGoodsByCode(this.goodsCode).then(res => {
@@ -150,12 +180,45 @@ export default {
           if (res.tags) {
             this.goodsForm.tags = this.goodsForm.tags.split(',')
           }
+          // 获取配置项数据
+          this.loadConfigOptions()
         })
       }
     },
+    // 加载配置项数据
+    async loadConfigOptions() {
+      if (!this.goodsId) return
+      try {
+        const res = await API_configOptions.getByProductId(this.goodsId)
+        this.configOptions = res
+      } catch (error) {
+        this.$message.error('加载配置项失败')
+      }
+    },
     handlePictureCardPreview(file) {
       this.dialogImageUrl = file.url
       this.dialogImage = true
+    },
+    // 获取配置项类型名称
+    getOptionTypeName(type) {
+      const types = {
+        'radio': '单选',
+        'checkbox': '多选',
+        'text': '文本',
+        'number': '数字'
+      }
+      return types[type] || '未知'
+    },
+
+    // 获取配置项类型标签样式
+    getOptionTypeTag(type) {
+      const types = {
+        'radio': 'primary',
+        'checkbox': 'success',
+        'text': 'info',
+        'number': 'warning'
+      }
+      return types[type] || 'info'
     }
   }
 }
@@ -167,6 +230,7 @@ export default {
 }
 
 div.base-info-item {
+  width: 100%;
   margin-top: 20px;
   h4 {
     margin-bottom: 10px;
@@ -216,5 +280,39 @@ div.base-info-item {
     line-height: 40px;
     width: 100%;
   }
+  .config-options {
+    width: 100%;
+    .config-option-item {
+      margin-bottom: 15px;
+      padding: 10px;
+      border: 1px solid #ebeef5;
+      border-radius: 4px;
+
+      .option-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .option-name {
+          font-weight: bold;
+          margin-right: 10px;
+        }
+      }
+
+      .option-values {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 5px;
+      }
+    }
+
+    .no-config {
+      color: #909399;
+      font-size: 14px;
+      text-align: center;
+      padding: 20px 0;
+    }
+  }
 }
+
 </style>

+ 100 - 2
manager-admin/src/views/goods/goodsEdit.vue

@@ -129,6 +129,32 @@
           <UE ref="ue" :defaultMsg="goodsForm.description"></UE>
         </el-form-item>
 
+        <!-- 添加配置项展示区域 -->
+        <el-form-item label="产品配置项:" class="config-options">
+          <div v-if="configOptions.length > 0">
+            <div v-for="option in configOptions" :key="option.id" class="config-option-item">
+              <div class="option-header">
+                <span class="option-name">{{ option.name }}</span>
+                <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+              </div>
+              <div class="option-values" v-if="option.option_values && option.option_values.length > 0">
+                <el-tag
+                  v-for="value in option.option_values"
+                  :key="value.id"
+                  size="small"
+                  :type="value.status === 1 ? 'success' : 'info'"
+                  style="margin-right: 5px; margin-bottom: 5px;"
+                >
+                  {{ value.value }}
+                </el-tag>
+              </div>
+            </div>
+          </div>
+          <div v-else class="no-config">
+            暂无配置项
+          </div>
+        </el-form-item>
+
         <el-form-item>
           <el-button type="primary" v-if="authStatus===0 || authStatus===3 || boolFounder" @click="handleSave" class="save">保存</el-button>
           <template v-if="goodsForm.enabled && goodsId">
@@ -164,6 +190,7 @@
 
 <script>
 import * as API_goods from '@/api/goods'
+import * as API_configOptions from '@/api/pjConfigOptions'
 import { UE, UploadSortable } from '@/components'
 import { RegExp } from '~/ui-utils'
 import Storage from '../../utils/storage'
@@ -240,7 +267,9 @@ export default {
       authDialogVisible: false,
       authStatus: 0,
       authPass: true,
-      authMsg: ''
+      authMsg: '',
+
+      configOptions: [] // 添加配置项数据
     }
   },
   mounted() {
@@ -259,7 +288,7 @@ export default {
     }
   },
   methods: {
-    getData() {
+    async getData() {
       API_goods.getGoods(this.goodsId).then(res => {
         console.log(res)
         this.goodsForm = res
@@ -275,8 +304,43 @@ export default {
         this.authStatus = res.auth_status
         this.authPass = this.authStatus === 2
         this.authMsg = res.auth_msg
+        // 获取配置项数据
+        this.loadConfigOptions()
       })
     },
+    // 加载配置项数据
+    async loadConfigOptions() {
+      if (!this.goodsId) return
+      try {
+        const res = await API_configOptions.getByProductId(this.goodsId)
+        this.configOptions = res
+      } catch (error) {
+        this.$message.error('加载配置项失败')
+      }
+    },
+
+    // 获取配置项类型名称
+    getOptionTypeName(type) {
+      const types = {
+        'radio': '单选',
+        'checkbox': '多选',
+        'text': '文本',
+        'number': '数字'
+      }
+      return types[type] || '未知'
+    },
+
+    // 获取配置项类型标签样式
+    getOptionTypeTag(type) {
+      const types = {
+        'radio': 'primary',
+        'checkbox': 'success',
+        'text': 'info',
+        'number': 'warning'
+      }
+      return types[type] || 'info'
+    },
+
     handleSave() {
       this.$refs['goodsForm'].validate((valid) => {
         if (valid) {
@@ -399,6 +463,7 @@ export default {
         return false
       }
     },
+
     /** 视频上传时进度条 */
     uploadVideoProcess(event, file, fileList) {
       this.videoErrorTip = false
@@ -530,4 +595,37 @@ div.base-info-item {
     width: 100%;
   }
 }
+
+.config-options {
+  .config-option-item {
+    margin-bottom: 15px;
+    padding: 10px;
+    border: 1px solid #ebeef5;
+    border-radius: 4px;
+
+    .option-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 10px;
+
+      .option-name {
+        font-weight: bold;
+        margin-right: 10px;
+      }
+    }
+
+    .option-values {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 5px;
+    }
+  }
+
+  .no-config {
+    color: #909399;
+    font-size: 14px;
+    text-align: center;
+    padding: 20px 0;
+  }
+}
 </style>

+ 10 - 3
manager-admin/src/views/goods/goodsList.vue

@@ -70,6 +70,13 @@
           <span v-else>empty</span>
         </template>
       </el-table-column>
+      <el-table-column label="是否可以" prop="enabled" width="100">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.enabled ? 'success' : 'danger'" size="mini" effect="dark">
+            {{ scope.row.enabled ? '可用' : '不可用' }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" width="150" fixed="right">
         <template slot-scope="scope">
           <el-button
@@ -86,7 +93,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -108,8 +115,8 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10,
-          sort: 'create_time',
+          page_size: 20,
+          sort: 'enabled',
           dir: 'desc'
         },
 

+ 2 - 2
manager-admin/src/views/goods/goodsListView.vue

@@ -75,7 +75,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -97,7 +97,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           sort: 'create_time',
           dir: 'desc'
         },

+ 50 - 8
manager-admin/src/views/goods/goodsSelect.vue

@@ -19,10 +19,6 @@
     </div>
 
     <template slot="table-columns">
-      <el-table-column
-        type="selection"
-        width="55">
-      </el-table-column>
       <el-table-column label="产品名称" min-width="250">
         <template slot-scope="scope">
           <div class="goods-info">
@@ -58,6 +54,17 @@
           <span v-else>empty</span>
         </template>
       </el-table-column>
+      <el-table-column label="操作" width="100" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="primary"
+            :disabled="isSelected(scope.row)"
+            @click="handleSelect(scope.row)">
+            {{ isSelected(scope.row) ? '已选择' : '选择' }}
+          </el-button>
+        </template>
+      </el-table-column>
     </template>
 
     <el-pagination
@@ -66,7 +73,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -92,6 +99,10 @@ export default {
     type_base: {
       type: Boolean,
       default: false
+    },
+    selectedGoods: {
+      type: Array,
+      default: () => []
     }
   },
   data() {
@@ -102,7 +113,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         sort: 'create_time',
         dir: 'desc'
       },
@@ -139,9 +150,28 @@ export default {
   methods: {
     /** 选择行变化时,记录选中的行数据 */
     selectFun(val) {
-      this.multipleSelection = val
+      // 如果已经选中了一个商品,且当前选择的是新商品,则取消之前的选择
+      if (this.multipleSelection.length === 1 && val.length > 1) {
+        this.$refs.table.clearSelection()
+        this.$refs.table.toggleRowSelection(val[val.length - 1])
+        this.multipleSelection = [val[val.length - 1]]
+      } else {
+        this.multipleSelection = val
+      }
+    },
+    /** 判断行是否可选 */
+    isSelectable(row) {
+      // 如果已经选中了一个商品,且不是当前行,则不可选
+      if (this.multipleSelection.length === 1 && !this.multipleSelection.includes(row)) {
+        return false
+      }
+      return true
     },
     handleConfirm() {
+      if (this.multipleSelection.length === 0) {
+        this.$message.warning('请选择一个商品')
+        return
+      }
       this.$emit('selected', this.multipleSelection)
     },
     /** 表头排序 **/
@@ -192,7 +222,7 @@ export default {
 
     GET_GoodsList() {
       this.loading = true
-      this.params.fixedCondition = ` FIND_IN_SET('${this.type}',type) and type_base=${this.type_base}`
+      this.params.fixedCondition = ` enabled = 1 and FIND_IN_SET('${this.type}',type) and type_base=${this.type_base}`
       API_goods.getGoodsList(this.params).then(response => {
         this.loading = false
         this.tableData = response
@@ -206,6 +236,18 @@ export default {
     },
     handleOperateGoods(id) {
       this.$router.push({ name: 'goodsEdit', params: { id: id, callback: this.GET_GoodsList }})
+    },
+    /** 判断商品是否已被选择 */
+    isSelected(row) {
+      return this.selectedGoods.some(item => item.good_id === row.id)
+    },
+    /** 选择商品 */
+    handleSelect(row) {
+      if (this.isSelected(row)) {
+        this.$message.warning('该商品已被选择')
+        return
+      }
+      this.$emit('selected', [row])
     }
   }
 }

+ 500 - 0
manager-admin/src/views/goodsItem/itemList.vue

@@ -0,0 +1,500 @@
+<template>
+  <div class="config-container">
+    <!-- 搜索区域 -->
+    <div class="search-section">
+      <div class="search-inputs">
+        <el-input
+          v-model="params.order_name"
+          placeholder="搜索配置项名称"
+          prefix-icon="el-icon-search"
+          clearable
+          @clear="searchOption"
+          @input="searchOption"
+        />
+        <el-input
+          v-model="params.keyword"
+          placeholder="搜索商品名称"
+          prefix-icon="el-icon-search"
+          clearable
+          @clear="searchOption"
+          @input="searchOption"
+        />
+      </div>
+      <el-button type="primary" @click="addOptionValues">新增配置项</el-button>
+    </div>
+
+    <!-- 配置项列表 -->
+    <el-table :data="optionList.data" style="margin-top: 20px;">
+      <el-table-column prop="name" label="配置项名称" width="160"/>
+      <el-table-column prop="type" label="类型" width="110">
+        <template slot-scope="scope">
+          <el-tag :type="getTypeTag(scope.row.type)">
+            {{ getTypeLabel(scope.row.type) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="values" label="可选内容">
+        <template slot-scope="scope">
+          <span v-if="scope.row.option_values && scope.row.option_values.length">
+            {{ scope.row.option_values.map(v => v.value).join(' / ') }}
+          </span>
+          <span v-else>—</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="适用商品" min-width="200">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.product_ids != null ||  scope.row.product_ids != ''"
+            v-for="(productName, index) in scope.row.show_product_names"
+            :key="index"
+            style="margin-right: 5px; cursor: pointer;"
+            @click="handleProductTagClick(productName)"
+            closable
+            @close="handleProductTagClose(scope.row, index)"
+          >
+            {{ productName }}
+          </el-tag>
+          <el-button
+            type="text"
+            size="mini"
+            @click="showProductSelector(scope.row)"
+          >
+            选择商品
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="180">
+        <template slot-scope="scope">
+          <el-button size="mini" @click="editOption(scope.row)">编辑</el-button>
+          <el-button size="mini" type="danger" @click="deleteOption(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 新增/编辑配置项弹窗 -->
+    <el-dialog :title="editMode ? '编辑配置项' : '新增配置项'" :visible.sync="showAddDialog" width="50%">
+      <el-form :model="optionForm" label-width="120px" class="option-form">
+        <el-form-item label="配置项名称" prop="name">
+          <el-input v-model="optionForm.name" style="width: 400px;"/>
+        </el-form-item>
+        <el-form-item label="类型" prop="type">
+          <el-select v-model="optionForm.type" style="width: 400px;">
+            <el-option label="单选" value="radio"/>
+            <el-option label="多选" value="checkbox"/>
+            <el-option label="文本" value="text"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="可选内容" v-if="optionForm.type === 'radio' || optionForm.type === 'checkbox'" prop="option_values">
+          <div class="option-values">
+            <div v-for="(item, idx) in optionForm.option_values" :key="idx" class="option-item">
+              <el-input v-show="item.status"
+                v-model="optionForm.option_values[idx].value"
+                placeholder="输入一个选项"
+                style="width: 400px;"
+              >
+                <el-button
+                  slot="append"
+                  icon="el-icon-delete"
+                  @click="removeOption(item, idx)"
+                  type="danger"
+                ></el-button>
+              </el-input>
+            </div>
+            <el-button size="mini" type="primary" @click="optionForm.option_values.push({value: '', status: 1})">
+              <i class="el-icon-plus"></i> 添加选项
+            </el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="showAddDialog = false">取消</el-button>
+        <el-button type="primary" @click="saveOption">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 选择商品弹窗 -->
+    <el-dialog title="选择适用商品" :visible.sync="showProductSelectorDialog" width="70%">
+      <div class="product-selector">
+        <div class="search-section">
+          <el-input
+            v-model="productSearchQuery"
+            placeholder="搜索商品名称"
+            prefix-icon="el-icon-search"
+            clearable
+            @clear="handleProductSearch"
+            @input="handleProductSearch"
+            style="width: 300px; margin-right: 10px;"
+          />
+        </div>
+
+        <div class="product-list">
+          <el-table :data="filteredProducts.data" style="width: 100%" height="400" @selection-change="handleProductSelectionChange" v-loading="loading">
+            <el-table-column type="selection" width="55"></el-table-column>
+            <el-table-column prop="name" label="商品名称" min-width="200"></el-table-column>
+            <el-table-column prop="no" label="生产型号" width="160"></el-table-column>
+            <el-table-column prop="type" label="类型" width="120"></el-table-column>
+          </el-table>
+          <el-pagination
+            v-if="filteredProducts"
+            slot="pagination"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+            :current-page="filteredProducts.page_no"
+            :page-sizes="[20, 50, 100, 200]"
+            :page-size="filteredProducts.page_size"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="filteredProducts.data_total">
+          </el-pagination>
+        </div>
+      </div>
+      <div slot="footer">
+        <el-button @click="showProductSelectorDialog = false">取消</el-button>
+        <el-button type="primary" @click="saveProductSelection">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-pagination
+      v-if="optionList"
+      slot="pagination"
+      @size-change="handlePageSizeChange"
+      @current-change="handlePageCurrentChange"
+      :current-page="optionList.page_no"
+      :page-sizes="[20, 50, 100, 200]"
+      :page-size="optionList.page_size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="optionList.data_total">
+    </el-pagination>
+  </div>
+</template>
+
+<script>
+import * as API_goods from '@/api/goods'
+import * as API_configOptions from '@/api/pjConfigOptions'
+
+export default {
+  name: 'pjGoodsList',
+  data() {
+    return {
+      products: [],
+      optionList: [],
+      searchQuery: '',
+      showAddDialog: false,
+      showProductSelectorDialog: false,
+      editMode: false,
+      optionForm: {
+        id: null,
+        name: '',
+        type: 'radio',
+        option_values: [{ value: '', status: 1 }],
+        productIds: []
+      },
+      currentEditingOption: null,
+      selectedProductIds: [],
+      selectedProductNames: [],
+      productSearchQuery: '',
+      filteredProducts: [],
+      loading: false,
+      checkIds: [],
+      checkNames: [],
+      goodParams: {
+        page_no: 1,
+        page_size: 20,
+        fixedCondition: ' enabled = 1 ',
+        sort: 'create_time',
+        dir: 'desc'
+      },
+      configLoading: false,
+      /** 列表参数 */
+      params: {
+        page_no: 1,
+        page_size: 20,
+        sort: 'create_time',
+        dir: 'desc'
+      }
+    }
+  },
+  created() {
+    this.loadOptions()
+  },
+  methods: {
+    async loadOptions() {
+      this.configLoading = true
+      try {
+        let res = await API_configOptions.getList(this.params)
+        res.data.forEach(item => {
+          if (item.product_names) {
+            item.show_product_names = item.product_names.split('=')
+          }
+          if (item.product_ids) {
+            item.show_product_ids = item.product_ids.split(',')
+          }
+        })
+        this.optionList = res
+      } catch (error) {
+        this.$message.error('加载配置项失败')
+      } finally {
+        this.configLoading = false
+      }
+    },
+    /** 分页大小发生改变 */
+    handlePageSizeChange(size) {
+      this.params.page_size = size
+      this.GET_GoodsList()
+    },
+    /** 分页页数发生改变 */
+    handlePageCurrentChange(page) {
+      this.params.page_no = page
+      this.GET_GoodsList()
+    },
+    addOptionValues() {
+      this.editMode = false
+      this.optionForm = {
+        id: null,
+        name: '',
+        type: 'radio',
+        option_values: [{ value: '', status: 1 }]
+      }
+      this.showAddDialog = true
+    },
+    getTypeLabel(type) {
+      const types = {
+        radio: '单选',
+        checkbox: '多选',
+        text: '文本'
+      }
+      return types[type] || type
+    },
+    getTypeTag(type) {
+      const types = {
+        radio: 'primary',
+        checkbox: 'success',
+        text: 'info'
+      }
+      return types[type] || 'info'
+    },
+    searchOption() {
+      this.params.page_no = 1
+      this.loadOptions()
+    },
+    handleProductTagClick(name) {
+      this.params.keyword = name
+      this.searchOption()
+    },
+    async handleProductTagClose(row, index) {
+      try {
+        await this.$confirm('确定删除商品吗?')
+        row.show_product_ids.splice(index, 1)
+        row.show_product_names.splice(index, 1)
+        const data = {
+          id: row.id,
+          product_ids: row.show_product_ids.join(','),
+          product_names: row.show_product_names.join('=')
+        }
+        await API_configOptions.editModel(data)
+        this.$message.success('删除成功')
+        this.loadOptions() // 重新加载列表
+      } catch (error) {
+        if (error !== 'cancel') {
+          this.$message.error('删除失败')
+        }
+      }
+    },
+    showProductSelector(option) {
+      this.currentEditingOption = option
+      if (option.show_product_ids) {
+        this.checkIds = [...option.show_product_ids]
+        this.checkNames = [...option.show_product_names]
+      } else {
+        this.checkIds = []
+        this.checkNames = []
+      }
+      this.loadProducts()
+      this.showProductSelectorDialog = true
+    },
+    async loadProducts() {
+      this.loading = true
+      try {
+        this.filteredProducts = await API_goods.getGoodsList(this.goodParams)
+      } catch (error) {
+        this.$message.error('加载商品列表失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSizeChange(val) {
+      this.goodParams.page_size = val
+      this.loadProducts()
+    },
+    handleCurrentChange(val) {
+      this.goodParams.page_no = val
+      this.loadProducts()
+    },
+    handleProductSearch() {
+      this.goodParams = {
+        ...this.goodParams,
+        query: this.productSearchQuery
+      }
+      this.loadProducts()
+    },
+    handleProductSelectionChange(selection) {
+      this.selectedProductIds = selection.map(item => item.id)
+      this.selectedProductNames = selection.map(item => item.name)
+    },
+    async saveProductSelection() {
+      if (this.currentEditingOption) {
+        try {
+          // 过滤掉已存在的商品ID,避免重复添加
+          this.checkIds.map(id => this.selectedProductIds.push(Number(id)))
+          this.checkNames.map(name => this.selectedProductNames.push(name))
+          // 数组去重
+          this.selectedProductIds = [...new Set(this.selectedProductIds)]
+          this.selectedProductNames = [...new Set(this.selectedProductNames)]
+          const productNames = this.selectedProductNames.join('=')
+          const productIds = this.selectedProductIds.join(',')
+          await API_configOptions.editModel({
+            id: this.currentEditingOption.id,
+            product_ids: productIds,
+            product_names: productNames
+          })
+          this.currentEditingOption.productIds = [...this.selectedProductIds]
+          this.$message.success('保存成功')
+          this.loadOptions() // 重新加载列表
+        } catch (error) {
+          this.$message.error('保存失败')
+        }
+      }
+      this.showProductSelectorDialog = false
+    },
+    editOption(row) {
+      this.editMode = true
+      this.optionForm = JSON.parse(JSON.stringify(row))
+      this.showAddDialog = true
+    },
+    async deleteOption(row) {
+      try {
+        await this.$confirm('确定删除该配置项吗?')
+        await API_configOptions.deletes(row.id)
+        this.$message.success('删除成功')
+        this.loadOptions() // 重新加载列表
+      } catch (error) {
+        if (error !== 'cancel') {
+          this.$message.error('删除失败')
+        }
+      }
+    },
+    removeOption(item, index) {
+      if (this.optionForm.option_values.length === 1 || this.optionForm.option_values.filter(v => v.status === 1).length === 1) {
+        this.$message.warning('至少保留一个选项')
+        return
+      }
+      this.$confirm('确定要删除这个选项吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        if (this.editMode && item.id) {
+          this.optionForm.option_values[index].status = 0
+          // API_configOptions.deletesOptionValues(item.id)
+        } else {
+          this.optionForm.option_values.splice(index, 1)
+        }
+        this.$message.success('删除成功')
+      }).catch(() => {})
+    },
+    async saveOption() {
+      if (!this.optionForm.name) {
+        this.$message.warning('请输入配置项名称')
+        return
+      }
+      if ((this.optionForm.type === 'radio' || this.optionForm.type === 'checkbox') && !this.optionForm.option_values) {
+        this.$message.warning('请填写可选内容')
+        return
+      }
+      try {
+        if (this.editMode) {
+          await API_configOptions.update(this.optionForm)
+        } else {
+          const data = {
+            name: this.optionForm.name,
+            type: this.optionForm.type,
+            values: this.optionForm.option_values.map(v => v.value)
+          }
+          await API_configOptions.addModel(data)
+        }
+        this.$message.success('保存成功')
+        this.showAddDialog = false
+        this.editMode = false
+        this.optionForm = { id: null, name: '', type: 'radio', option_values: [{ value: '', status: 1 }], productIds: [] }
+        this.loadOptions() // 重新加载列表
+      } catch (error) {
+        this.$message.error('保存失败')
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.config-container {
+  padding: 20px;
+}
+
+.search-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.search-inputs {
+  display: flex;
+  gap: 10px;
+  flex: 1;
+  margin-right: 20px;
+}
+
+.search-inputs .el-input {
+  width: 300px;
+}
+
+.search-inputs .el-select {
+  width: 200px;
+}
+
+.option-form {
+  padding: 20px;
+}
+
+.option-values {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.option-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.product-selector {
+  .search-section {
+    margin-bottom: 20px;
+    display: flex;
+    align-items: center;
+  }
+
+  .product-list {
+    margin-top: 20px;
+  }
+}
+
+.el-tag {
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.el-tag:hover {
+  opacity: 0.8;
+}
+</style>
+

+ 2 - 2
manager-admin/src/views/member/member-manage/memberList.vue

@@ -91,7 +91,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -194,7 +194,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 列表数据 */

+ 2 - 2
manager-admin/src/views/member/member-manage/memberListAddress.vue

@@ -20,7 +20,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -42,7 +42,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 列表数据 */

+ 2 - 2
manager-admin/src/views/member/member-manage/memberListAsk.vue

@@ -31,7 +31,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -75,7 +75,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           member_id: this.memberId
         },
         // 列表数据

+ 2 - 2
manager-admin/src/views/member/member-manage/memberListComments.vue

@@ -31,7 +31,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -77,7 +77,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           member_id: this.memberId
         },
         // 列表数据

+ 2 - 2
manager-admin/src/views/member/member-manage/memberListOrder.vue

@@ -29,7 +29,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -50,7 +50,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           member_id: this.memberId
         },
         // 列表数据

+ 2 - 2
manager-admin/src/views/member/member-manage/memberListPoints.vue

@@ -34,7 +34,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -55,7 +55,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           member_id: this.memberId
         },
         // 列表数据

+ 2 - 2
manager-admin/src/views/member/member-manage/memberRecycle.vue

@@ -80,7 +80,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -101,7 +101,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           disabled: -1
         },
 

+ 2 - 2
manager-admin/src/views/member/pre-deposit/preDepositDetail.vue

@@ -57,7 +57,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -78,7 +78,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 列表数据 */

+ 2 - 2
manager-admin/src/views/member/pre-deposit/rechargeList.vue

@@ -62,7 +62,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -83,7 +83,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 列表数据 */

+ 2 - 2
manager-admin/src/views/order/collectionList.vue

@@ -84,7 +84,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -109,7 +109,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 列表数据 */

+ 2 - 2
manager-admin/src/views/order/complaint/complaintList.vue

@@ -55,7 +55,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -73,7 +73,7 @@
           tableData: {},
           params: {
             page_no: 1,
-            page_size: 10
+            page_size: 20
           },
           /** 高级搜索数据 */
           advancedForm: {}

+ 2 - 2
manager-admin/src/views/order/complaint/subjectList.vue

@@ -34,7 +34,7 @@
           @size-change="handlePageSizeChange"
           @current-change="handlePageCurrentChange"
           :current-page="tableData.page_no"
-          :page-sizes="[10, 20, 50, 100]"
+          :page-sizes="[20, 50, 100, 200]"
           :page-size="tableData.page_size"
           layout="total, sizes, prev, pager, next, jumper"
           :total="tableData.data_total">
@@ -74,7 +74,7 @@
           /** 列表参数 */
           params: {
             page_no: 1,
-            page_size: 10
+            page_size: 20
           },
 
           /** 列表数据 */

+ 2 - 2
manager-admin/src/views/order/orderList.vue

@@ -125,7 +125,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -147,7 +147,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         // 列表数据
         tableData: '',

+ 2 - 2
manager-admin/src/views/order/receiptExamine.vue

@@ -74,7 +74,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -142,7 +142,7 @@
           // 列表参数
           params: {
             page_no: 1,
-            page_size: 10
+            page_size: 20
           },
           advancedForm: {},
           // 列表数据

+ 2 - 2
manager-admin/src/views/order/receiptHistory.vue

@@ -89,7 +89,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -109,7 +109,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         // 列表数据
         tableData: '',

+ 2 - 2
manager-admin/src/views/order/refundList.vue

@@ -86,7 +86,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="pageData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="pageData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="pageData.data_total">
@@ -108,7 +108,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         /** 列表数据 */
         tableData: null,

+ 2 - 2
manager-admin/src/views/order/serviceList.vue

@@ -91,7 +91,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="pageData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="pageData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="pageData.data_total">
@@ -113,7 +113,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         /** 列表数据 */
         tableData: null,

+ 371 - 15
manager-admin/src/views/pjDraft/FsGoods.vue

@@ -12,7 +12,7 @@
             <el-card shadow="always" :class="item.id?'green-border':''">
               <div slot="header" class="clearfix">
                 <span>{{ item.name }}</span>
-                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id)">删除</el-button>
+                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id, item.good_id, 0)">删除</el-button>
                 <el-button style="float: right; padding: 3px 0;margin-right: 5px" type="text" icon="el-icon-edit-outline" @click="handleItemSave(item)">保存</el-button>
               </div>
               <div>
@@ -33,6 +33,55 @@
                     <div><b style="color:red">* </b>价格:<el-input v-model="item.price" size="small" type="number" style="width: 100px"></el-input></div>
                   </div>
                 </div>
+                <div v-if="item.config_options && item.config_options.length > 0" class="config-options">
+                  <el-divider content-position="left">配置项</el-divider>
+                  <div v-for="option in item.config_options" :key="option.id" class="config-item">
+                    <div class="config-header">
+                      <span class="config-name">{{ option.name }}</span>
+                      <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+                      <!-- <el-tag size="small" type="danger" v-if="option.required">必选</el-tag> -->
+                      <el-tag size="small" type="danger">必选</el-tag>
+                    </div>
+                    <div class="config-values">
+                      <el-radio-group v-if="option.type === 'radio'" v-model="option.selected_value" size="small">
+                        <el-radio
+                          v-for="value in option.option_values"
+                          :key="value.id"
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-radio>
+                      </el-radio-group>
+
+                      <el-checkbox-group v-if="option.type === 'checkbox'" v-model="option.selected_values" size="small">
+                        <el-checkbox
+                          v-for="value in option.option_values"
+                          :key="value.id"
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-checkbox>
+                      </el-checkbox-group>
+
+                      <el-input
+                        v-if="option.type === 'text'"
+                        v-model="option.selected_value"
+                        size="small"
+                        placeholder="请输入"
+                      ></el-input>
+
+                      <el-input-number
+                        v-if="option.type === 'number'"
+                        v-model="option.selected_value"
+                        size="small"
+                        :min="0"
+                        :precision="0"
+                      ></el-input-number>
+                    </div>
+                  </div>
+                </div>
                 <div>
                   <el-input type="textarea" rows="3" placeholder="备注,最大300字" maxlength="300" show-word-limit v-model="item.sales_remark"></el-input>
                 </div>
@@ -46,7 +95,7 @@
             <el-card shadow="always" :class="item.id?'green-border':''" style="margin-top:15px;">
               <div slot="header" class="clearfix">
                 <span>{{ item.name }}</span>
-                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id)">删除</el-button>
+                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id, item.good_id, 1)">删除</el-button>
                 <el-button style="float: right; padding: 3px 0;margin-right: 5px" type="text" icon="el-icon-edit-outline" @click="handleItemSave(item)">保存</el-button>
               </div>
               <div>
@@ -67,6 +116,55 @@
                     <div><b style="color:red">* </b>价格:<el-input v-model="item.price" size="small" type="number" style="width: 100px"></el-input></div>
                   </div>
                 </div>
+                <div v-if="item.config_options && item.config_options.length > 0" class="config-options">
+                  <el-divider content-position="left">配置项</el-divider>
+                  <div v-for="option in item.config_options" :key="option.id" class="config-item">
+                    <div class="config-header">
+                      <span class="config-name">{{ option.name }}</span>
+                      <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+                      <!-- <el-tag size="small" type="danger" v-if="option.required">必选</el-tag> -->
+                      <el-tag size="small" type="danger">必选</el-tag>
+                    </div>
+                    <div class="config-values">
+                      <el-radio-group v-if="option.type === 'radio'" v-model="option.selected_value" size="small">
+                        <el-radio
+                          v-for="value in option.option_values"
+                          :key="value.id"
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-radio>
+                      </el-radio-group>
+
+                      <el-checkbox-group v-if="option.type === 'checkbox'" v-model="option.selected_values" size="small">
+                        <el-checkbox
+                          v-for="value in option.option_values"
+                          :key="value.id"
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-checkbox>
+                      </el-checkbox-group>
+
+                      <el-input
+                        v-if="option.type === 'text'"
+                        v-model="option.selected_value"
+                        size="small"
+                        placeholder="请输入"
+                      ></el-input>
+
+                      <el-input-number
+                        v-if="option.type === 'number'"
+                        v-model="option.selected_value"
+                        size="small"
+                        :min="0"
+                        :precision="0"
+                      ></el-input-number>
+                    </div>
+                  </div>
+                </div>
                 <div>
                   <el-input type="textarea" rows="3" placeholder="备注,最大300字" maxlength="300" show-word-limit v-model="item.sales_remark"></el-input>
                 </div>
@@ -88,6 +186,26 @@
           <el-table-column prop="no" label="生产编码"></el-table-column>
           <el-table-column prop="qty" label="数量"></el-table-column>
           <el-table-column prop="price" label="价格"></el-table-column>
+          <el-table-column label="配置项" min-width="300">
+            <template slot-scope="scope">
+              <div v-if="scope.row.config_options && scope.row.config_options.length > 0">
+                <div v-for="option in scope.row.config_options" :key="option.id" class="config-item-row">
+                  <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ option.name }}:</el-tag>
+                  <span v-if="option.type === 'checkbox'">
+                    <el-tag size="small" type="info" v-for="value in option.selected_values" :key="value" style="margin-left: 5px">
+                      {{ value }}
+                    </el-tag>
+                  </span>
+                  <span v-else>
+                    <el-tag size="small" type="info" style="margin-left: 5px">
+                      {{ option.selected_value }}
+                    </el-tag>
+                  </span>
+                </div>
+              </div>
+              <span v-else>无配置项</span>
+            </template>
+          </el-table-column>
           <el-table-column prop="sales_remark" label="销售备注"></el-table-column>
         </el-table>
         <div style="margin-top: 20px">
@@ -101,13 +219,19 @@
       :append-to-body="true"
       size="80%"
     >
-      <goods-select :type="type" :type_base="type_base" @selected="goodsSelect"></goods-select>
+      <goods-select
+        :type="type"
+        :type_base="type_base"
+        :selectedGoods="listData"
+        @selected="goodsSelect">
+      </goods-select>
     </el-drawer>
   </div>
 </template>
 
 <script>
 import * as API_draftGoods from '@/api/pjDraftGoods.js'
+import * as API_configOptions from '@/api/pjConfigOptions'
 import { Foundation } from '~/ui-utils'
 import goodsSelect from '../goods/goodsSelect.vue'
 
@@ -155,10 +279,56 @@ export default {
     }
   },
   methods: {
-    getData() {
+    async getData() {
       this.params.fixedCondition = 'draft_id=' + this.draftId
-      API_draftGoods.getList(this.params).then(res => {
+      API_draftGoods.getList(this.params).then(async res => {
         this.listData = res.data
+        // 收集所有商品的ID
+        const productIds = this.listData.map(item => item.good_id)
+        try {
+          // 批量获取所有商品的配置项
+          let configRes = await API_configOptions.getByProductIds(productIds)
+          configRes.map(item => {
+            item.product_ids = item.product_ids.split(',')
+          })
+          // 处理每个商品的配置项
+          this.listData.forEach(item => {
+            // 获取该商品的配置项
+            const itemConfigs = configRes.filter(config => config.product_ids.indexOf(item.good_id + '') !== -1)
+            // 初始化配置项数据
+            const configOptions = itemConfigs.map(option => ({
+              ...option,
+              selected_value: '',
+              selected_values: []
+            }))
+            // 如果有已保存的配置数据,合并到配置项中
+            if (item.config_data) {
+              try {
+                const savedConfig = JSON.parse(item.config_data)
+                configOptions.forEach(option => {
+                  const savedOption = savedConfig.find(c => c.id === option.id)
+                  if (savedOption) {
+                    if (option.type === 'checkbox') {
+                      option.selected_values = savedOption.value ? savedOption.value.split(',') : []
+                    } else {
+                      option.selected_value = savedOption.value || ''
+                    }
+                  }
+                })
+              } catch (error) {
+                console.error('解析配置数据失败:', error)
+              }
+            }
+            // 使用 Vue.set 确保数据响应式
+            this.$set(item, 'config_options', configOptions)
+          })
+        } catch (error) {
+          console.error('获取配置项失败:', error)
+          // 如果获取失败,为每个商品设置空配置项
+          this.listData.forEach(item => {
+            this.$set(item, 'config_options', [])
+          })
+        }
         this.baseDevice = this.listData.filter(p => p.type_base)
         this.addonDevice = this.listData.filter(p => !p.type_base)
       })
@@ -171,23 +341,53 @@ export default {
       this.type_base = false
       this.goodsSelectVisible = true
     },
-    goodsSelect(list) {
-      let data = JSON.parse(JSON.stringify(list))
-      data.forEach(item => {
+    async goodsSelect(list) {
+      // 只取第一个商品
+      const item = JSON.parse(JSON.stringify(list[0]))
+      if (!item) return
+
+      // 检查是否已存在该商品
+      if (this.listData.filter(p => p.good_id === item.id).length > 0) {
+        this.$message.warning('该商品已添加')
+        this.goodsSelectVisible = false
+        return
+      }
+
+      // 获取配置项
+      try {
+        const configRes = await API_configOptions.getByProductId(item.id)
         item.good_id = item.id
         item.id = null
-        if (this.listData.filter(p => p.good_id === item.good_id).length === 0) {
-          item.qty = 1
-          item.price = item.price_advise
-          this.listData.push(item)
-        }
-      })
+        item.qty = 1
+        item.price = item.price_advise
+        // 使用 Vue.set 确保数据响应式
+        this.$set(item, 'config_options', configRes.map(option => ({
+          ...option,
+          selected_value: '',
+          selected_values: []
+        })))
+      } catch (error) {
+        console.error('获取配置项失败:', error)
+        this.$set(item, 'config_options', [])
+      }
+
+      // 添加到列表
+      this.listData.push(item)
       this.baseDevice = this.listData.filter(p => p.type_base)
       this.addonDevice = this.listData.filter(p => !p.type_base)
       this.goodsSelectVisible = false
     },
-    handleItemDelete(id) {
+    handleItemDelete(id, goodId, type) {
       this.$confirm('确认删除, 是否继续?', '提示', { type: 'warning' }).then(() => {
+        if (!id) {
+          if (type === 0) {
+            this.baseDevice.splice(this.baseDevice.findIndex(p => p.good_id === goodId), 1)
+          } else {
+            this.addonDevice.splice(this.addonDevice.findIndex(p => p.good_id === goodId), 1)
+          }
+          this.listData.splice(this.listData.findIndex(p => p.good_id === goodId), 1)
+          return
+        }
         API_draftGoods.deletes(id).then(() => {
           this.getData()
           this.$message.success('删除成功!')
@@ -199,10 +399,39 @@ export default {
         this.$message.error('数量和价格不能为空')
         return false
       }
+      // 检查必选配置项是否已填写
+      if (item.config_options && item.config_options.length > 0) {
+        const missingRequired = item.config_options.find(option => {
+          if (option.type === 'checkbox') {
+            return !option.selected_values || option.selected_values.length === 0
+          } else {
+            return !option.selected_value
+          }
+        })
+        if (missingRequired) {
+          this.$message.error(`请填写必选配置项:${missingRequired.name}`)
+          return false
+        }
+      }
+      // 处理配置项数据
+      const config_data = item.config_options.map(option => ({
+        id: option.id,
+        name: option.name,
+        type: option.type,
+        value: option.type === 'checkbox' ? option.selected_values.join(',') : option.selected_value
+      }))
+
       item.draft_id = this.draftId
+      item.config_data = JSON.stringify(config_data)
+
       if (item.id) {
         API_draftGoods.editModel(item).then(res => {
           this.$message.success('修改成功!')
+          // 更新本地数据
+          const index = this.listData.findIndex(p => p.id === item.id)
+          if (index !== -1) {
+            this.$set(this.listData, index, { ...item })
+          }
         })
       } else {
         API_draftGoods.addModel(item).then(res => {
@@ -230,6 +459,24 @@ export default {
         }))
       }
       this.MixinExportJosnToExcel(json, '产品列表')
+    },
+    getOptionTypeName(type) {
+      const types = {
+        'radio': '单选',
+        'checkbox': '多选',
+        'text': '文本',
+        'number': '数字'
+      }
+      return types[type] || '未知'
+    },
+    getOptionTypeTag(type) {
+      const types = {
+        'radio': 'primary',
+        'checkbox': 'success',
+        'text': 'info',
+        'number': 'warning'
+      }
+      return types[type] || 'info'
     }
   }
 }
@@ -264,4 +511,113 @@ export default {
 /deep/ .el-drawer__body{
   overflow-y: scroll;
 }
+.config-options {
+  margin: 10px 0;
+
+  .config-item {
+    margin-bottom: 15px;
+
+    .config-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+
+      .config-name {
+        font-weight: bold;
+        margin-right: 10px;
+      }
+    }
+
+    .config-values {
+      padding-left: 10px;
+
+      .el-radio-group,
+      .el-checkbox-group {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+      }
+
+      .el-input,
+      .el-input-number {
+        width: 200px;
+      }
+    }
+  }
+}
+.config-selector {
+  padding: 20px;
+
+  .goods-info {
+    margin-bottom: 20px;
+
+    h3 {
+      margin: 0 0 10px 0;
+    }
+
+    p {
+      color: #666;
+      margin: 0;
+    }
+  }
+
+  .config-item {
+    margin-bottom: 20px;
+
+    .config-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 10px;
+
+      .config-name {
+        font-weight: bold;
+        margin-right: 10px;
+      }
+    }
+
+    .config-values {
+      padding-left: 10px;
+
+      .el-radio-group,
+      .el-checkbox-group {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+      }
+
+      .el-input,
+      .el-input-number {
+        width: 200px;
+      }
+    }
+  }
+
+  .drawer-footer {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 20px;
+    background: #fff;
+    border-top: 1px solid #e4e7ed;
+    text-align: right;
+  }
+}
+
+// 在商品卡片中添加配置项按钮样式
+.config-button {
+  margin-top: 10px;
+  text-align: right;
+}
+
+.config-item-row {
+  margin-bottom: 5px;
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
 </style>

+ 436 - 0
manager-admin/src/views/pjDraft/FsGoods2.vue

@@ -0,0 +1,436 @@
+<template>
+  <div class="container">
+    <el-alert type="warning"
+              :title="type+'项目条目'"
+              description="选择主机,选择从设备,再其它可选"
+              :closable="false" show-icon style="margin-bottom: 15px"></el-alert>
+    <el-tabs>
+      <el-tab-pane label="图表">
+        <el-divider>标准配置 <el-button type="text" icon="el-icon-circle-plus-outline" @click="handleAddBase">添加</el-button></el-divider>
+        <el-row :gutter="24" type="flex" style="flex-wrap: wrap; flex-direction: row">
+          <el-col :span="8" v-for="item in baseDevice" :key="item.id">
+            <el-card shadow="always" :class="item.id?'green-border':''">
+              <div slot="header" class="clearfix">
+                <span>{{ item.name }}</span>
+                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id)">删除</el-button>
+                <el-button style="float: right; padding: 3px 0;margin-right: 5px" type="text" icon="el-icon-edit-outline" @click="handleItemSave(item)">保存</el-button>
+              </div>
+              <div>
+                <div class="param-item">
+                  <div class="image">
+                    <el-link
+                      @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }"
+                    >
+                      <img :src="item.image">
+                    </el-link>
+                  </div>
+                  <div class="info">
+                    <div>生产型号:{{ item.code }}</div>
+                    <div>产品编码:{{ item.no }}</div>
+                    <div>建议售价:¥{{ item.price_advise }}</div>
+                    <div>生产周期:{{ item.produce_day }}天</div>
+                    <div><b style="color:red">* </b>数量:<el-input v-model="item.qty" size="small" type="number" style="width: 100px"></el-input></div>
+                    <div><b style="color:red">* </b>价格:<el-input v-model="item.price" size="small" type="number" style="width: 100px"></el-input></div>
+                  </div>
+                </div>
+                <div class="config-options" v-if="item.config_options && item.config_options.length > 0">
+                  <el-divider content-position="left">配置项</el-divider>
+                  <div v-for="option in item.config_options" :key="option.id" class="config-item">
+                    <div class="config-header">
+                      <span class="config-name">{{ option.name }}</span>
+                      <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+                    </div>
+                    <div class="config-values">
+                      <el-radio-group v-if="option.type === 'radio'" v-model="option.selected_value" size="small">
+                        <el-radio 
+                          v-for="value in option.option_values" 
+                          :key="value.id" 
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-radio>
+                      </el-radio-group>
+                      
+                      <el-checkbox-group v-if="option.type === 'checkbox'" v-model="option.selected_values" size="small">
+                        <el-checkbox 
+                          v-for="value in option.option_values" 
+                          :key="value.id" 
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-checkbox>
+                      </el-checkbox-group>
+                      
+                      <el-input 
+                        v-if="option.type === 'text'" 
+                        v-model="option.selected_value" 
+                        size="small"
+                        placeholder="请输入"
+                      ></el-input>
+                      
+                      <el-input-number 
+                        v-if="option.type === 'number'" 
+                        v-model="option.selected_value" 
+                        size="small"
+                        :min="0"
+                        :precision="0"
+                      ></el-input-number>
+                    </div>
+                  </div>
+                </div>
+                <div>
+                  <el-input type="textarea" rows="3" placeholder="备注,最大300字" maxlength="300" show-word-limit v-model="item.sales_remark"></el-input>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-divider>其它选配 <el-button type="text" icon="el-icon-circle-plus-outline" @click="handleAddAddon">添加</el-button></el-divider>
+        <el-row :gutter="24" type="flex" style="flex-wrap: wrap; flex-direction: row">
+          <el-col :span="8" v-for="item in addonDevice" :key="item.id">
+            <el-card shadow="always" :class="item.id?'green-border':''" style="margin-top:15px;">
+              <div slot="header" class="clearfix">
+                <span>{{ item.name }}</span>
+                <el-button style="float: right; padding: 3px 0;" type="text" icon="el-icon-circle-close" @click="handleItemDelete(item.id)">删除</el-button>
+                <el-button style="float: right; padding: 3px 0;margin-right: 5px" type="text" icon="el-icon-edit-outline" @click="handleItemSave(item)">保存</el-button>
+              </div>
+              <div>
+                <div class="param-item">
+                  <div class="image">
+                    <el-link
+                      @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }"
+                    >
+                      <img :src="item.image">
+                    </el-link>
+                  </div>
+                  <div class="info">
+                    <div>生产型号:{{ item.code }}</div>
+                    <div>产品编码:{{ item.no }}</div>
+                    <div>建议售价:¥{{ item.price_advise }}</div>
+                    <div>生产周期:{{ item.produce_day }}天</div>
+                    <div><b style="color:red">* </b>数量:<el-input v-model="item.qty" size="small" type="number" style="width: 100px"></el-input></div>
+                    <div><b style="color:red">* </b>价格:<el-input v-model="item.price" size="small" type="number" style="width: 100px"></el-input></div>
+                  </div>
+                </div>
+                <div class="config-options" v-if="item.config_options && item.config_options.length > 0">
+                  <el-divider content-position="left">配置项</el-divider>
+                  <div v-for="option in item.config_options" :key="option.id" class="config-item">
+                    <div class="config-header">
+                      <span class="config-name">{{ option.name }}</span>
+                      <el-tag size="small" :type="getOptionTypeTag(option.type)">{{ getOptionTypeName(option.type) }}</el-tag>
+                    </div>
+                    <div class="config-values">
+                      <el-radio-group v-if="option.type === 'radio'" v-model="option.selected_value" size="small">
+                        <el-radio 
+                          v-for="value in option.option_values" 
+                          :key="value.id" 
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-radio>
+                      </el-radio-group>
+                      
+                      <el-checkbox-group v-if="option.type === 'checkbox'" v-model="option.selected_values" size="small">
+                        <el-checkbox 
+                          v-for="value in option.option_values" 
+                          :key="value.id" 
+                          :label="value.value"
+                          :disabled="value.status !== 1"
+                        >
+                          {{ value.value }}
+                        </el-checkbox>
+                      </el-checkbox-group>
+                      
+                      <el-input 
+                        v-if="option.type === 'text'" 
+                        v-model="option.selected_value" 
+                        size="small"
+                        placeholder="请输入"
+                      ></el-input>
+                      
+                      <el-input-number 
+                        v-if="option.type === 'number'" 
+                        v-model="option.selected_value" 
+                        size="small"
+                        :min="0"
+                        :precision="0"
+                      ></el-input-number>
+                    </div>
+                  </div>
+                </div>
+                <div>
+                  <el-input type="textarea" rows="3" placeholder="备注,最大300字" maxlength="300" show-word-limit v-model="item.sales_remark"></el-input>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+      <el-tab-pane label="表格">
+        <el-table :data="listData" stripe style="width: 100%;">
+          <el-table-column prop="name" label="名称"></el-table-column>
+          <el-table-column label="基础件?">
+            <template slot-scope="scope">
+              <el-tag type="success" v-if="scope.row.type_base">标准件</el-tag>
+              <el-tag type="info" v-else>配件</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="code" label="生产型号"></el-table-column>
+          <el-table-column prop="no" label="生产编码"></el-table-column>
+          <el-table-column prop="qty" label="数量"></el-table-column>
+          <el-table-column prop="price" label="价格"></el-table-column>
+          <el-table-column prop="sales_remark" label="销售备注"></el-table-column>
+        </el-table>
+        <div style="margin-top: 20px">
+          <el-button type="primary" @click="outExcel">导出Excel</el-button>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+    <el-drawer
+      title="设备"
+      :visible.sync="goodsSelectVisible"
+      :append-to-body="true"
+      size="80%"
+    >
+      <goods-select :type="type" :type_base="type_base" @selected="goodsSelect"></goods-select>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import * as API_draftGoods from '@/api/pjDraftGoods.js'
+import * as API_configOptions from '@/api/pjConfigOptions'
+import { Foundation } from '~/ui-utils'
+import goodsSelect from '../goods/goodsSelect.vue'
+
+export default {
+  name: 'draftFsGoods',
+  components: { goodsSelect },
+  props: {
+    draftId: {
+      type: Number,
+      value: 0
+    },
+    type: {
+      type: String,
+      value: '服务器版'
+    }
+  },
+  data() {
+    return {
+      keyId: '',
+      params: {
+        page_no: 1,
+        page_size: 1000,
+        sort: 'type_base',
+        dir: 'desc'
+      },
+      listData: [],
+      baseDevice: [],
+      addonDevice: [],
+
+      goodsSelectVisible: false,
+      type_base: true
+    }
+  },
+  mounted() {
+    console.log(this.$route.params)
+    if (this.$route.params && this.$route.params.id !== 0) {
+      this.keyId = this.$route.params.id
+      this.getData()
+    }
+  },
+  activated() {
+    if (this.$route.params && this.$route.params.id !== 0) {
+      this.keyId = this.$route.params.id
+      this.getData()
+    }
+  },
+  methods: {
+    async getData() {
+      this.params.fixedCondition = 'draft_id=' + this.draftId
+      API_draftGoods.getList(this.params).then(async res => {
+        this.listData = res.data
+        for (let item of this.listData) {
+          try {
+            const configRes = await API_configOptions.getByProductId(item.good_id)
+            item.config_options = configRes.map(option => ({
+              ...option,
+              selected_value: '',
+              selected_values: []
+            }))
+          } catch (error) {
+            console.error('获取配置项失败:', error)
+            item.config_options = []
+          }
+        }
+        this.baseDevice = this.listData.filter(p => p.type_base)
+        this.addonDevice = this.listData.filter(p => !p.type_base)
+      })
+    },
+    handleAddBase() {
+      this.type_base = true
+      this.goodsSelectVisible = true
+    },
+    handleAddAddon() {
+      this.type_base = false
+      this.goodsSelectVisible = true
+    },
+    goodsSelect(list) {
+      let data = JSON.parse(JSON.stringify(list))
+      data.forEach(item => {
+        item.good_id = item.id
+        item.id = null
+        if (this.listData.filter(p => p.good_id === item.good_id).length === 0) {
+          item.qty = 1
+          item.price = item.price_advise
+          this.listData.push(item)
+        }
+      })
+      this.baseDevice = this.listData.filter(p => p.type_base)
+      this.addonDevice = this.listData.filter(p => !p.type_base)
+      this.goodsSelectVisible = false
+    },
+    handleItemDelete(id) {
+      this.$confirm('确认删除, 是否继续?', '提示', { type: 'warning' }).then(() => {
+        API_draftGoods.deletes(id).then(() => {
+          this.getData()
+          this.$message.success('删除成功!')
+        })
+      })
+    },
+    handleItemSave(item) {
+      if (!(item.qty && item.price)) {
+        this.$message.error('数量和价格不能为空')
+        return false
+      }
+      
+      const config_data = item.config_options.map(option => ({
+        id: option.id,
+        type: option.type,
+        value: option.type === 'checkbox' ? option.selected_values.join(',') : option.selected_value
+      }))
+      
+      item.draft_id = this.draftId
+      item.config_data = JSON.stringify(config_data)
+      
+      if (item.id) {
+        API_draftGoods.editModel(item).then(res => {
+          this.$message.success('修改成功!')
+        })
+      } else {
+        API_draftGoods.addModel(item).then(res => {
+          this.$message.success('添加成功!')
+          this.listData.forEach(p => {
+            if (p.good_id === item.good_id) {
+              p.id = res.data.id
+              this.baseDevice = this.listData.filter(p => p.type_base)
+              this.addonDevice = this.listData.filter(p => !p.type_base)
+            }
+          })
+        })
+      }
+    },
+    outExcel() {
+      const json = {
+        sheet_name: '产品列表',
+        sheet_values: this.listData.map(item => ({
+          '产品名称': item.name,
+          '生产型号': item.code,
+          '生产编码': item.no,
+          '数量': item.qty,
+          '单价': Foundation.formatPrice(item.price),
+          '备注': item.sales_remark
+        }))
+      }
+      this.MixinExportJosnToExcel(json, '产品列表')
+    },
+    getOptionTypeName(type) {
+      const types = {
+        'radio': '单选',
+        'checkbox': '多选',
+        'text': '文本',
+        'number': '数字'
+      }
+      return types[type] || '未知'
+    },
+    getOptionTypeTag(type) {
+      const types = {
+        'radio': 'primary',
+        'checkbox': 'success',
+        'text': 'info',
+        'number': 'warning'
+      }
+      return types[type] || 'info'
+    }
+  }
+}
+</script>
+
+<style type="text/scss" lang="scss" scoped>
+.param-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 7px;
+  font-size: 14px;
+
+  .image {
+    width: 38%;
+  }
+  .image img {
+    width: 100px;
+    height: 100px;
+  }
+  .info div {
+    margin-bottom: 5px;
+  }
+
+  &.empty {
+    background-color: #fff
+  }
+}
+.green-border {
+  border-color: #13ce66;
+}
+/deep/ .el-drawer__body{
+  overflow-y: scroll;
+}
+.config-options {
+  margin: 10px 0;
+  
+  .config-item {
+    margin-bottom: 15px;
+    
+    .config-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      
+      .config-name {
+        font-weight: bold;
+        margin-right: 10px;
+      }
+    }
+    
+    .config-values {
+      padding-left: 10px;
+      
+      .el-radio-group,
+      .el-checkbox-group {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+      }
+      
+      .el-input,
+      .el-input-number {
+        width: 200px;
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
manager-admin/src/views/pjDraft/List.vue

@@ -51,7 +51,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -73,7 +73,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         sort: 'create_time',
         dir: 'desc'
       },

+ 2 - 2
manager-admin/src/views/pjDraft/SelfList.vue

@@ -51,7 +51,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -74,7 +74,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         sort: 'create_time',
         dir: 'desc'
       },

+ 4 - 4
manager-admin/src/views/pjOrder/Items.vue

@@ -110,7 +110,7 @@
           @size-change="handlePageSizeChange"
           @current-change="handlePageCurrentChange"
           :current-page="tableData.page_no"
-          :page-sizes="[10, 20, 50, 100]"
+          :page-sizes="[20, 50, 100, 200]"
           :page-size="tableData.page_size"
           layout="total, sizes, prev, pager, next, jumper"
           :total="tableData.data_total">
@@ -155,7 +155,7 @@
           @size-change="handlePageSizeChange2"
           @current-change="handlePageCurrentChange2"
           :current-page="tableData2.page_no"
-          :page-sizes="[10, 20, 50, 100]"
+          :page-sizes="[20, 50, 100, 200]"
           :page-size="tableData2.page_size"
           layout="total, sizes, prev, pager, next, jumper"
           :total="tableData2.data_total">
@@ -180,7 +180,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         type: 0
       },
 
@@ -193,7 +193,7 @@ export default {
       tableData2: [],
       params2: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         type: 1,
         good_id: 1
       },

+ 2 - 2
manager-admin/src/views/pjOrder/List.vue

@@ -80,7 +80,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -105,7 +105,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         sort: 'create_time',
         dir: 'desc'
       },

+ 652 - 0
manager-admin/src/views/pjOrder/OrderItems copy.vue

@@ -0,0 +1,652 @@
+<template>
+  <div class="container">
+    <el-alert type="warning"
+              :title="type+'项目条目'"
+              :closable="false" show-icon style="margin-bottom: 15px"></el-alert>
+
+    <el-tabs>
+      <el-tab-pane label="图表">
+        <el-divider>标准配置</el-divider>
+        <el-row :gutter="24" type="flex" style="flex-wrap: wrap; flex-direction: row">
+          <el-col :span="12" v-for="(item, index) in baseDevice" :key="item.id">
+            <el-card shadow="always">
+              <div slot="header" class="clearfix">
+                <el-link @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }">{{ item.name }}</el-link>
+                <el-button v-if="boolFounder" size="mini" @click="changeEdit(item)">修改</el-button>
+              </div>
+              <div>
+                <div class="param-item">
+                  <div class="image">
+                    <el-link @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }">
+                      <img :src="item.image">
+                    </el-link>
+                  </div>
+                  <div class="info">
+                    <div>生产型号:{{ item.code }}</div>
+                    <div>产品编码:{{ item.no }}</div>
+                    <div>产品类型:{{ item.good_type }}</div>
+                    <div>生产周期:{{ item.produce_day }}天</div>
+                    <div v-if="boolFounder">数量:<el-input-number v-model="item.qty"/><el-button @click="handleSave(item)">保存</el-button></div>
+                    <div v-else>数量:{{item.qty}}</div>
+                    <div v-if="boolFounder || permissions.filter(p => p === 'draftEdit' || p === 'pjReceipt').length > 0">价格:{{item.price}}</div>
+                  </div>
+                </div>
+                <el-tooltip class="item" effect="dark" :content="item.sales_remark" placement="top">
+                  <div class="myFont"><span style="font-size: 11px">{{ item.sales_remark }}</span></div>
+                </el-tooltip>
+                <el-row>
+                  <el-col :span="12">
+                    <template v-if="boolFounder || permissions.filter(p => p === 'pjTech' || p === 'pjOrderItemsList').length > 0">
+                      <el-divider>技术:
+                        <el-button type="text" icon="el-icon-edit-outline" @click="handleTechSave(item)">保存</el-button>
+                      </el-divider>
+                      <div class="tech_panel">
+                        <div class="myFont">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_name" placement="top">
+                            <span style="font-size: 11px">{{item.tech_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div class="myFont">填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_time" placement="top">
+                            <span style="font-size: 11px">{{ item.tech_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>硬件版本:<el-input v-model="item.hard_ver" size="small"></el-input></div>
+                        <div>软件版本:<el-input v-model="item.soft_ver" size="small"></el-input></div>
+                        <div>备注:<el-input type="textarea" rows="3" placeholder="备注,最大150字" maxlength="150" show-word-limit v-model="item.tech_remark"></el-input></div>
+                        <div @click="clickImg(index, 1)">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.tech_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" :limit="1">
+                            <el-button v-if="!item.tech_img" size="small" type="primary">点击上传</el-button>
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                    <template v-else>
+                      <el-divider>技术:</el-divider>
+                      <div class="tech_panel myFont">
+                        <div class="">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_name" placement="top">
+                            <span style="font-size: 11px">{{item.tech_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_time" placement="top">
+                            <span style="font-size: 11px">{{ item.tech_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>硬件版本:<el-tooltip class="item" effect="dark" :content="item.hard_ver" placement="top">
+                          <span style="font-size: 11px">{{item.hard_ver}}</span>
+                        </el-tooltip>
+                        </div>
+                        <div>软件版本:<el-tooltip class="item" effect="dark" :content="item.soft_ver" placement="top">
+                          <span style="font-size: 11px">{{item.soft_ver}}</span>
+                        </el-tooltip></div>
+                        <div>备注:<el-tooltip class="item" effect="dark" :content="item.tech_remark" placement="top">
+                          <span style="font-size: 11px">{{item.tech_remark}}</span>
+                        </el-tooltip></div>
+                        <div v-if="item.tech_img">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.tech_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" disabled:limit="1">
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                  </el-col>
+                  <el-col :span="12">
+                    <template v-if="boolFounder || permissions.filter(p => p === 'pjOrderItemsList').length > 0">
+                      <el-divider>生产:
+                        <el-button type="text" icon="el-icon-edit-outline" @click="handleProduceSave(item)">保存</el-button>
+                      </el-divider>
+                      <div class="tech_panel">
+                        <div class="myFont">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_name" placement="top">
+                            <span style="font-size: 11px">{{item.produce_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div class="myFont">填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_time" placement="top">
+                            <span style="font-size: 11px">{{ item.produce_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>已产量:<el-input v-model="item.produced_qty"><template slot="append">共{{item.qty}}</template></el-input></div>
+                        <div>发货量:<el-input v-model="item.shipped_qty" class="input-with-select">
+                          <el-select v-model="item.shipped_add" slot="suffix" placeholder="发货地">
+                            <el-option label="深圳" value="深圳"></el-option>
+                            <el-option label="长沙" value="长沙"></el-option>
+                          </el-select>
+                        </el-input></div>
+                        <div>备注:
+                          <el-input type="textarea" rows="3" placeholder="备注,最大150字" maxlength="150" show-word-limit v-model="item.produced_remark"></el-input>
+                        </div>
+                        <div>生产状态:
+                          <el-select placeholder="请选择" slot="prepend" v-model="item.produce_status">
+                            <el-option value="采购原料中"></el-option>
+                            <el-option value="生产中"></el-option>
+                            <el-option value="完成"></el-option>
+                          </el-select>
+                        </div>
+                        <div @click="clickImg(index, 2)">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.produce_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" :limit="1">
+                            <el-button v-if="!item.produce_img" size="small" type="primary">点击上传</el-button>
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                    <template v-else>
+                      <el-divider>生产情况</el-divider>
+                      <div class="tech_panel myFont">
+                        <div>填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_name" placement="top">
+                            <span style="font-size: 11px">{{item.produce_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_time" placement="top">
+                            <span style="font-size: 11px">{{ item.produce_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>已产量:<el-tooltip class="item" effect="dark" :content="item.produced_qty" placement="top">
+                          <span style="font-size: 11px">{{ item.produced_qty }}</span>
+                        </el-tooltip>件</div>
+                        <div>发货量:<el-tooltip class="item" effect="dark" :content="item.shipped_qty" placement="top">
+                          <span style="font-size: 11px">{{ item.shipped_add }}发{{ item.shipped_qty }}件</span>
+                        </el-tooltip>件</div>
+                        <div>备注:<el-tooltip class="item" effect="dark" :content="item.produced_remark" placement="top">
+                          <span style="font-size: 11px">{{ item.produced_remark }}</span>
+                        </el-tooltip></div>
+                        <div>生产状态:<span style="font-size: 11px">{{ item.produce_status }}</span></div>
+                        <div v-if="item.produce_img">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.produce_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" disabled :limit="1">
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                  </el-col>
+                </el-row>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-divider>其它选配</el-divider>
+        <el-row :gutter="24" type="flex" style="flex-wrap: wrap; flex-direction: row">
+          <el-col :span="12" v-for="item in addonDevice" :key="item.id">
+            <el-card shadow="always">
+              <div slot="header" class="clearfix">
+                <el-link @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }">{{ item.name }}</el-link>
+                <el-button v-if="boolFounder" size="mini" @click="changeEdit(item)">修改</el-button>
+              </div>
+              <div>
+                <div class="param-item">
+                  <div class="image">
+                    <el-link @click="() => { $router.push({ path: `/pj/pj-goods-detail/${item.good_id}` }) }">
+                      <img :src="item.image">
+                    </el-link>
+                  </div>
+                  <div class="info">
+                    <div>生产型号:{{ item.code }}</div>
+                    <div>产品编码:{{ item.no }}</div>
+                    <div>产品类型:{{ item.good_type }}</div>
+                    <div>生产周期:{{ item.produce_day }}天</div>
+                    <div v-if="boolFounder">数量:<el-input-number v-model="item.qty"/><el-button @click="handleSave(item)">保存</el-button></div>
+                    <div v-else>数量:{{item.qty}}</div>
+                    <div v-if="boolFounder || permissions.filter(p => p === 'draftEdit' || p === 'pjReceipt').length > 0">价格:{{item.price}}</div>
+                  </div>
+                </div>
+                <el-tooltip class="item" effect="dark" :content="item.sales_remark" placement="top">
+                  <div class="myFont"><span style="font-size: 11px">{{ item.sales_remark }}</span></div>
+                </el-tooltip>
+                <el-row>
+                  <el-col :span="12">
+                    <template v-if="boolFounder || permissions.filter(p => p === 'pjTech' || p === 'pjOrderItemsList').length > 0">
+                      <el-divider>技术:
+                        <el-button type="text" icon="el-icon-edit-outline" @click="handleTechSave(item)">保存</el-button>
+                      </el-divider>
+                      <div class="tech_panel">
+                        <div class="myFont">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_name" placement="top">
+                            <span style="font-size: 11px">{{item.tech_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div class="myFont">填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_time" placement="top">
+                            <span style="font-size: 11px">{{ item.tech_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>硬件版本:<el-input v-model="item.hard_ver" size="small"></el-input></div>
+                        <div>软件版本:<el-input v-model="item.soft_ver" size="small"></el-input></div>
+                        <div>备注:<el-input type="textarea" rows="3" placeholder="备注,最大150字" maxlength="150" show-word-limit v-model="item.tech_remark"></el-input></div>
+                        <div @click="clickImg(index, 1)">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.tech_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" :limit="1">
+                            <el-button v-if="!item.tech_img" size="small" type="primary">点击上传</el-button>
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                    <template v-else>
+                      <el-divider>技术:</el-divider>
+                      <div class="tech_panel myFont">
+                        <div class="">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_name" placement="top">
+                            <span style="font-size: 11px">{{item.tech_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.tech_time" placement="top">
+                            <span style="font-size: 11px">{{ item.tech_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>硬件版本:<el-tooltip class="item" effect="dark" :content="item.hard_ver" placement="top">
+                          <span style="font-size: 11px">{{item.hard_ver}}</span>
+                        </el-tooltip>
+                        </div>
+                        <div>软件版本:<el-tooltip class="item" effect="dark" :content="item.soft_ver" placement="top">
+                          <span style="font-size: 11px">{{item.soft_ver}}</span>
+                        </el-tooltip></div>
+                        <div>备注:<el-tooltip class="item" effect="dark" :content="item.tech_remark" placement="top">
+                          <span style="font-size: 11px">{{item.tech_remark}}</span>
+                        </el-tooltip></div>
+                        <div v-if="item.tech_img">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.tech_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" disabled:limit="1">
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                  </el-col>
+                  <el-col :span="12">
+                    <template v-if="boolFounder || permissions.filter(p => p === 'pjOrderItemsList').length > 0">
+                      <el-divider>生产:
+                        <el-button type="text" icon="el-icon-edit-outline" @click="handleProduceSave(item)">保存</el-button>
+                      </el-divider>
+                      <div class="tech_panel">
+                        <div class="myFont">填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_name" placement="top">
+                            <span style="font-size: 11px">{{item.produce_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div class="myFont">填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_time" placement="top">
+                            <span style="font-size: 11px">{{ item.produce_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>已产量:<el-input v-model="item.produced_qty"><template slot="append">共{{item.qty}}</template></el-input></div>
+                        <div>发货量:<el-input v-model="item.shipped_qty" class="input-with-select">
+                          <el-select v-model="item.shipped_add" slot="suffix" placeholder="发货地">
+                            <el-option label="深圳" value="深圳"></el-option>
+                            <el-option label="长沙" value="长沙"></el-option>
+                          </el-select>
+                        </el-input></div>
+                        <div>备注:
+                          <el-input type="textarea" rows="3" placeholder="备注,最大150字" maxlength="150" show-word-limit v-model="item.produced_remark"></el-input>
+                        </div>
+                        <div>生产状态:
+                          <el-select placeholder="请选择" slot="prepend" v-model="item.produce_status">
+                            <el-option value="采购原料中"></el-option>
+                            <el-option value="生产中"></el-option>
+                            <el-option value="完成"></el-option>
+                          </el-select>
+                        </div>
+                        <div @click="clickImg(index, 2)">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.produce_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" :limit="1">
+                            <el-button v-if="!item.produce_img" size="small" type="primary">点击上传</el-button>
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                    <template v-else>
+                      <el-divider>生产情况</el-divider>
+                      <div class="tech_panel myFont">
+                        <div>填写人:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_name" placement="top">
+                            <span style="font-size: 11px">{{item.produce_name}}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>填写时间:
+                          <el-tooltip class="item" effect="dark" :content="item.produce_time" placement="top">
+                            <span style="font-size: 11px">{{ item.produce_time }}</span>
+                          </el-tooltip>
+                        </div>
+                        <div>已产量:<el-tooltip class="item" effect="dark" :content="item.produced_qty" placement="top">
+                          <span style="font-size: 11px">{{ item.produced_qty }}</span>
+                        </el-tooltip>件</div>
+                        <div>发货量:<el-tooltip class="item" effect="dark" :content="item.shipped_qty" placement="top">
+                          <span style="font-size: 11px">{{ item.shipped_add }}发{{ item.shipped_qty }}件</span>
+                        </el-tooltip>件</div>
+                        <div>备注:<el-tooltip class="item" effect="dark" :content="item.produced_remark" placement="top">
+                          <span style="font-size: 11px">{{ item.produced_remark }}</span>
+                        </el-tooltip></div>
+                        <div>生产状态:<span style="font-size: 11px">{{ item.produce_status }}</span></div>
+                        <div v-if="item.produce_img">
+                          <el-upload :action="`${MixinUploadApi}?scene=pjorders`" :file-list="getImg(item.name, item.produce_img)" :on-preview="handlePictureCardPreview" :before-upload="beforeAvatarUpload"
+                                     :on-success="handleImageSuccess" :on-remove="handleRemove" :on-exceed="handleExceed" disabled :limit="1">
+                          </el-upload>
+                        </div>
+                      </div>
+                    </template>
+                  </el-col>
+                </el-row>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+      <el-tab-pane label="表格">
+        <el-table :data="listData" stripe style="width: 100%;">
+          <el-table-column prop="name" show-overflow-tooltip label="名称" min-width="140"></el-table-column>
+          <el-table-column label="基础件?">
+            <template slot-scope="scope">
+              <el-tag type="success" v-if="scope.row.type_base">标准件</el-tag>
+              <el-tag type="info" v-else>配件</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="code" show-overflow-tooltip label="生产型号"></el-table-column>
+          <el-table-column prop="no" show-overflow-tooltip label="生产编码"></el-table-column>
+          <el-table-column prop="hard_ver" show-overflow-tooltip label="硬件版本"></el-table-column>
+          <el-table-column prop="soft_ver" show-overflow-tooltip label="软件版本"></el-table-column>
+          <el-table-column prop="good_type" label="类型" width="100"></el-table-column>
+          <el-table-column prop="qty" label="数量" width="100"></el-table-column>
+          <el-table-column prop="produced_qty" label="已产数量" width="100"></el-table-column>
+          <el-table-column prop="produced_qty" label="需产数量" width="100">
+            <template slot-scope="scope">
+              <span>{{ scope.row.qty - scope.row.produced_qty}}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="shipped_qty" label="已发数量" width="100"></el-table-column>
+          <el-table-column prop="shipped_add" label="发货地" width="80"></el-table-column>
+          <el-table-column v-if="boolFounder || permissions.filter(p => p === 'draftEdit' || p === 'pjReceipt').length > 0" prop="price" label="价格" width="100"></el-table-column>
+          <el-table-column prop="sales_remark" show-overflow-tooltip label="销售备注"></el-table-column>
+          <el-table-column prop="produced_remark" show-overflow-tooltip label="生产备注"></el-table-column>
+        </el-table>
+        <div style="margin-top: 20px">
+          <el-button type="primary" @click="outExcel">导出Excel</el-button>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+    <el-dialog :visible.sync="dialogImage">
+      <img width="100%" :src="dialogImageUrl" alt="">
+    </el-dialog>
+    <el-drawer
+      title="设备"
+      :visible.sync="goodsSelectVisible"
+      :append-to-body="true"
+      size="80%"
+    >
+      <goods-select :type="type" :type_base="type_base" @selected="goodsSelect"></goods-select>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import * as API_orderItems from '@/api/pjOrderItems.js'
+import { Foundation } from '~/ui-utils'
+import Storage from '../../utils/storage'
+import goodsSelect from '../goods/goodsSelect.vue'
+import { editModel } from '../../api/pjOrderItems'
+
+export default {
+  name: 'pjOrderItems',
+  components: { goodsSelect },
+  props: {
+    orderNo: {
+      type: String,
+      value: ''
+    },
+    type: {
+      type: String,
+      value: '服务器版'
+    }
+  },
+  data() {
+    return {
+      boolFounder: JSON.parse(Storage.getItem('admin_user')).founder === 1,
+      permissions: [],
+      keyId: '',
+      params: {
+        page_no: 1,
+        page_size: 1000,
+        sort: 'type_base',
+        dir: 'desc'
+      },
+      listData: [],
+      baseDevice: [],
+      addonDevice: [],
+      dialogImage: false,
+      dialogImageUrl: '',
+      changeId: null,
+      index: 0,
+      imgType: 1,
+      goodsSelectVisible: false,
+      type_base: true
+    }
+  },
+  mounted() {
+    if (!this.boolFounder) {
+      this.permissions = JSON.parse(Storage.getItem('permissions'))
+    }
+  },
+  activated() {
+  },
+  watch: {
+    orderNo(val) {
+      if (val) {
+        this.getData()
+      }
+    }
+  },
+  methods: {
+    getData() {
+      this.params.fixedCondition = 'order_no=' + this.orderNo
+      API_orderItems.getList(this.params).then(res => {
+        this.listData = res.data
+        this.baseDevice = this.listData.filter(p => p.type_base)
+        this.addonDevice = this.listData.filter(p => !p.type_base)
+      })
+    },
+    handleSave(item) {
+      if (!this.boolFounder) {
+        this.$message.error('您没有权限!')
+        return
+      }
+      API_orderItems.editModel(item).then(res => {
+        this.$message.success('保存成功!')
+      })
+    },
+    handleTechSave(item) {
+      const adminUser = JSON.parse(Storage.getItem('admin_user'))
+      item.tech_user = adminUser.uid
+      if (item.tech_name) {
+        item.tech_name = item.tech_name + ',' + adminUser.realname
+      } else {
+        item.tech_name = adminUser.realname
+      }
+      if (item.tech_time) {
+        item.tech_time = item.tech_time + ',' + Foundation.unixToDate(Math.round(new Date() / 1000))
+      } else {
+        item.tech_time = Foundation.unixToDate(Math.round(new Date() / 1000))
+      }
+      API_orderItems.editModelAndStatus(item, 1).then(res => {
+        this.$message.success('保存成功!')
+      })
+    },
+    handleProduceSave(item) {
+      if (item.produced_qty) {
+        if (item.produced_qty < 0) {
+          this.$message.error('数量不能小于0!')
+          return
+        }
+      }
+      const adminUser = JSON.parse(Storage.getItem('admin_user'))
+      item.produce_user = adminUser.uid
+      if (item.produce_name) {
+        item.produce_name = item.produce_name + ',' + adminUser.realname
+      } else {
+        item.produce_name = adminUser.realname
+      }
+      if (item.produce_time) {
+        item.produce_time = item.produce_time + ',' + Foundation.unixToDate(Math.round(new Date() / 1000))
+      } else {
+        item.produce_time = Foundation.unixToDate(Math.round(new Date() / 1000))
+      }
+      API_orderItems.editModelAndStatus(item, 2).then(res => {
+        this.$message.success('保存成功!')
+      })
+    },
+    outExcel() {
+      const json = {
+        sheet_name: '产品列表',
+        sheet_values: this.listData.map(item => ({
+          '产品名称': item.name,
+          '生产型号': item.code,
+          '生产编码': item.no,
+          '硬件版本': item.hard_ver,
+          '软件编码': item.soft_ver,
+          '产品类型': item.good_type,
+          '数量': item.qty,
+          '已产数量': item.produced_qty,
+          '需产数量': item.qty - item.produced_qty,
+          '已发数量': item.shipped_qty,
+          '发货地址': item.shipped_add,
+          '销售备注': item.sales_remark,
+          '生产备注': item.produced_remark
+        }))
+      }
+      this.MixinExportJosnToExcel(json, '产品列表')
+    },
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url
+      this.dialogImage = true
+    },
+    /** 图片上传之前的校验  */
+    beforeAvatarUpload(file) {
+      const isType = file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png'
+      const isLt1M = file.size / 1024 / 1024 < 3
+      if (!isType) {
+        this.$message.error('上传图片只能是 JPG/JPEG/PNG 格式!')
+      }
+      if (!isLt1M) {
+        this.$message.error('上传图片大小不能超过 3MB!')
+      }
+      return isType && isLt1M
+    },
+    /** 文件列表上传成功时的钩子  上传成功校验 */
+    async handleImageSuccess(response, file) {
+      if (file.status === 'success') {
+        if (this.imgType === 1) {
+          this.$set(this.baseDevice[this.index], 'tech_img', response.url)
+        } else {
+          this.$set(this.baseDevice[this.index], 'produce_img', response.url)
+        }
+      }
+    },
+    handleRemove(e) {
+      setTimeout(() => {
+        if (this.imgType === 1) {
+          this.$set(this.baseDevice[this.index], 'tech_img', null)
+        } else {
+          this.$set(this.baseDevice[this.index], 'produce_img', null)
+        }
+      }, 400)
+    },
+    handleExceed(e) {
+      this.$message.error('最多只能上传一个文件!')
+    },
+    clickImg(index, type) {
+      this.index = index
+      this.imgType = type
+    },
+    changeEdit(item) {
+      this.changeId = item.id
+      this.type_base = item.type_base
+      this.goodsSelectVisible = true
+    },
+    goodsSelect(list) {
+      if (list.length > 1) {
+        this.$message.error('只能选择一个产品!')
+        return
+      }
+      if (this.listData.findIndex(p => p.good_id === list[0].id) > -1) {
+        this.$message.error('产品已存在!')
+        return
+      }
+      const data = {
+        good_id: list[0].id,
+        name: list[0].name,
+        code: list[0].code,
+        no: list[0].no,
+        image: list[0].image,
+        id: this.changeId
+      }
+      API_orderItems.editModel(data).then(res => {
+        this.$message.success('保存成功!')
+        this.getData()
+      })
+      this.goodsSelectVisible = false
+    },
+    getImg(name, img) {
+      if (img) {
+        return [{ name: name + '备注图', url: img }]
+      } else {
+        return []
+      }
+    },
+    formartLen(str) {
+      console.log('str===', str)
+      if (!str) {
+        return ''
+      } else {
+        return str.length > 20 ? str.substr(0, 20) + '...' : str
+      }
+    }
+  }
+}
+</script>
+
+<style type="text/scss" lang="scss" scoped>
+.param-item {
+  display: flex;
+  align-items: center;
+  //justify-content: space-between;
+  padding: 3px;
+  font-size: 14px;
+
+  .image {
+    width: 38%;
+  }
+  .image img {
+    width: 100px;
+    height: 100px;
+  }
+  .info div {
+    margin-bottom: 5px;
+  }
+
+  &.empty {
+    background-color: #fff
+  }
+}
+.tech_panel div{
+  margin: 8px 5px 3px 3px;
+}
+.avatar {
+  width: 100px;
+  height: 100px;
+  display: block;
+}
+.myFont {
+  overflow: hidden;
+  text-overflow: ellipsis;      //超出部分以省略号显示
+  white-space: nowrap;
+  width: 16em;  //用px单位亦可行
+}
+.el-input .el-select {
+  width: 130px;
+}
+.input-with-select .el-input-group__prepend {
+  background-color: #fff;
+}
+</style>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1375 - 397
manager-admin/src/views/pjOrder/OrderItems.vue


+ 2 - 2
manager-admin/src/views/pjOrder/Receipt.vue

@@ -51,7 +51,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -110,7 +110,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10
+        page_size: 20
       },
 
       /** 商品列表数据 */

+ 2 - 2
manager-admin/src/views/pjOrder/SelfList.vue

@@ -61,7 +61,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -84,7 +84,7 @@ export default {
       /** 列表参数 */
       params: {
         page_no: 1,
-        page_size: 10,
+        page_size: 20,
         sort: 'create_time',
         dir: 'desc'
       },

+ 2 - 2
manager-admin/src/views/setting/auth-settings/administratorManage.vue

@@ -44,7 +44,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -139,7 +139,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         // 列表数据
         tableData: '',

+ 2 - 2
manager-admin/src/views/setting/auth-settings/roleManage.vue

@@ -35,7 +35,7 @@
       @size-change="handlePageSizeChange"
       @current-change="handlePageCurrentChange"
       :current-page="tableData.page_no"
-      :page-sizes="[10, 20, 50, 100]"
+      :page-sizes="[20, 50, 100, 200]"
       :page-size="tableData.page_size"
       layout="total, sizes, prev, pager, next, jumper"
       :total="tableData.data_total">
@@ -55,7 +55,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         // 列表数据
         tableData: ''

+ 2 - 2
manager-admin/src/views/setting/search-settings/searchHistory.vue

@@ -24,7 +24,7 @@
 			@size-change="handlePageSizeChange"
 			@current-change="handlePageCurrentChange"
 			:current-page="tableData.page_no"
-			:page-sizes="[10, 20, 50, 100]"
+			:page-sizes="[20, 50, 100, 200]"
 			:page-size="tableData.page_size"
 			layout="total, sizes, prev, pager, next, jumper"
 			:total="tableData.data_total">
@@ -45,7 +45,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 搜索列表数据 */

+ 2 - 2
manager-admin/src/views/setting/search-settings/searchKeyword.vue

@@ -37,7 +37,7 @@
 				@size-change="handlePageSizeChange"
 				@current-change="handlePageCurrentChange"
 				:current-page="tableData.page_no"
-				:page-sizes="[10, 20, 50, 100]"
+				:page-sizes="[20, 50, 100, 200]"
 				:page-size="tableData.page_size"
 				layout="total, sizes, prev, pager, next, jumper"
 				:total="tableData.data_total">
@@ -99,7 +99,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 搜索列表数据 */

+ 2 - 2
manager-admin/src/views/setting/search-settings/searchTips.vue

@@ -45,7 +45,7 @@
 				@size-change="handlePageSizeChange"
 				@current-change="handlePageCurrentChange"
 				:current-page="tableData.page_no"
-				:page-sizes="[10, 20, 50, 100]"
+				:page-sizes="[20, 50, 100, 200]"
 				:page-size="tableData.page_size"
 				layout="total, sizes, prev, pager, next, jumper"
 				:total="tableData.data_total">
@@ -86,7 +86,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
 
         /** 搜索列表数据 */

+ 2 - 2
manager-admin/src/views/shop/settlement-manage/settlement.vue

@@ -35,7 +35,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -56,7 +56,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10
+          page_size: 20
         },
         // 列表数据
         tableData: ''

+ 2 - 2
manager-admin/src/views/shop/settlement-manage/settlementList.vue

@@ -52,7 +52,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -73,7 +73,7 @@
         // 列表参数
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           sn: this.$route.params.sn
         },
         // 列表数据

+ 2 - 2
manager-admin/src/views/shop/shop-manage/shopAudit.vue

@@ -40,7 +40,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -62,7 +62,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           shop_disable: 'APPLY'
         },
 

+ 2 - 2
manager-admin/src/views/shop/shop-manage/shopList.vue

@@ -97,7 +97,7 @@
         @size-change="handlePageSizeChange"
         @current-change="handlePageCurrentChange"
         :current-page="tableData.page_no"
-        :page-sizes="[10, 20, 50, 100]"
+        :page-sizes="[20, 50, 100, 200]"
         :page-size="tableData.page_size"
         layout="total, sizes, prev, pager, next, jumper"
         :total="tableData.data_total">
@@ -118,7 +118,7 @@
         /** 列表参数 */
         params: {
           page_no: 1,
-          page_size: 10,
+          page_size: 20,
           shop_disable: 'ALL'
         },
         /** 列表数据 */