zlwu 5 meses atrás
pai
commit
82b57684a3

+ 2 - 1
art-design-pro/src/components/Table/ArtTable.vue

@@ -62,6 +62,7 @@
 
 <script setup lang="ts">
   import { computed } from 'vue'
+  import type { TableColumnCtx } from 'element-plus'
 
   interface TableProps {
     /** 表格数据源 */
@@ -113,7 +114,7 @@
     /** 是否显示合计行 */
     showSummary?: boolean
     /** 自定义合计行计算方法 */
-    summaryMethod?: (param: { columns: string[]; data: any[] }) => string[]
+    summaryMethod?: (param: { columns: TableColumnCtx<any>[]; data: any[] }) => string[]
   }
 
   const props = withDefaults(defineProps<TableProps>(), {

+ 5 - 1
art-design-pro/src/views/inventory/index.vue

@@ -350,6 +350,9 @@
         <el-form-item label="产品" prop="product">
           <el-select
             v-model="formData.product"
+            filterable
+            remote
+            :remote-method="loadCargos"
             placeholder="请选择产品"
             style="width: 100%"
             @focus="loadCargos('')"
@@ -520,6 +523,7 @@
 <script setup lang="ts">
   import { ref, reactive, computed, onMounted, nextTick } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import type { TableColumnCtx } from 'element-plus'
   import { Plus, Document, Search, Edit, Delete, View, Link } from '@element-plus/icons-vue'
   import { inventoryApi, type InventoryRecord, type InventoryCreate } from '@/api/inventoryApi'
   import { planApi, type Plan } from '@/api/plansApi'
@@ -661,7 +665,7 @@
   }
 
   // 表格合计行
-  const getSummary = (param: { columns: string[]; data: any[] }) => {
+  const getSummary = (param: { columns: TableColumnCtx<any>[]; data: any[] }) => {
     const { columns } = param
     const sums: string[] = []
 

+ 10 - 5
art-design-pro/src/views/plans/index.vue

@@ -226,7 +226,7 @@
               :inactive-value="0"
               active-text="开"
               inactive-text="关"
-              @change="val => handleToggleAutoGenerate(row, val)"
+              @change="val => handleToggleAutoGenerate(row, Number(val))"
             />
             <div v-if="row.autoGenerateEndMonth" style="font-size: 12px; color: #909399; margin-top: 4px;">
               结束:{{ row.autoGenerateEndMonth }}
@@ -336,6 +336,9 @@
             <el-form-item label="产品" prop="product">
               <el-select
                 v-model="formData.product"
+                filterable
+                remote
+                :remote-method="loadCargos"
                 placeholder="请选择产品"
                 style="width: 100%"
                 @focus="loadCargos('')"
@@ -482,6 +485,7 @@
 <script setup lang="ts">
   import { ref, reactive, computed, onMounted } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import type { TableColumnCtx } from 'element-plus'
   import { Plus, Upload, Document, Search, View, Edit, Delete, DocumentAdd, Link } from '@element-plus/icons-vue'
   import { planApi, type Plan, type PlanCreate } from '@/api/plansApi'
   import { companyApi, cargoApi, type Company, type Cargo } from '@/api/masterDataApi'
@@ -618,7 +622,7 @@
   }
 
   // 表格合计行
-  const getSummary = (param: { columns: string[]; data: any[] }) => {
+  const getSummary = (param: { columns: TableColumnCtx<any>[]; data: any[] }) => {
     const { columns } = param
     const sums: string[] = []
 
@@ -949,17 +953,18 @@
 
   // 列表开关:切换自动生成
   const handleToggleAutoGenerate = async (record: Plan, value: number) => {
+    const normalizedValue = value === 1 ? 1 : 0
     try {
       await planApi.update(record.id, {
-        autoGenerateEnabled: value,
+        autoGenerateEnabled: normalizedValue,
         autoGenerateEndMonth: record.autoGenerateEndMonth
       })
-      ElMessage.success(value === 1 ? '已开启自动生成' : '已关闭自动生成')
+      ElMessage.success(normalizedValue === 1 ? '已开启自动生成' : '已关闭自动生成')
       loadData()
     } catch (e: any) {
       ElMessage.error(e?.response?.data?.detail || e?.message || '更新失败')
       // 回滚开关显示
-      record.autoGenerateEnabled = value === 1 ? 0 : 1
+      record.autoGenerateEnabled = normalizedValue === 1 ? 0 : 1
     }
   }
 

+ 2 - 1
art-design-pro/src/views/weighing/index.vue

@@ -527,6 +527,7 @@
 <script setup lang="ts">
   import { ref, reactive, computed, onMounted } from 'vue'
   import { ElMessage, ElMessageBox } from 'element-plus'
+  import type { TableColumnCtx } from 'element-plus'
   import { Plus, Document, Search, Edit, Delete, Close, View } from '@element-plus/icons-vue'
   import { weighingApi, type WeighingRecord, type WeighingCreate } from '@/api/weighingApi'
   import { companyApi, cargoApi, type Company, type Cargo } from '@/api/masterDataApi'
@@ -731,7 +732,7 @@
   }
 
   // 表格合计行
-  const getSummary = (param: { columns: string[]; data: any[] }) => {
+  const getSummary = (param: { columns: TableColumnCtx<any>[]; data: any[] }) => {
     const { columns } = param
     const sums: string[] = []
 

+ 27 - 7
auto-create-plan/auto_create_monthly_plan.py

@@ -22,16 +22,36 @@ from typing import Optional
 
 from sqlalchemy import and_, or_, select
 from sqlalchemy.orm import Session
-from sqlmodel import SQLModel, create_engine
+from sqlmodel import Field, SQLModel, create_engine
 
-# 为了避免直接依赖 backend 包的相对导入,这里动态拼出项目根目录,
-# 然后从 backend.app.models.plans 导入 PurchasePlan 模型。
 ROOT_DIR = Path(__file__).resolve().parents[1]
-BACKEND_PATH = ROOT_DIR / "backend"
-if str(BACKEND_PATH) not in sys.path:
-    sys.path.insert(0, str(BACKEND_PATH))
 
-from app.models.plans import PurchasePlan  # type: ignore  # noqa: E402
+class PurchasePlan(SQLModel, table=True):
+    """计划表 - 对应 purchase_plans。"""
+
+    __tablename__ = "purchase_plans"
+
+    id: int | None = Field(default=None, primary_key=True)
+    plan_no: str | None = Field(default=None, max_length=255, index=True)
+    plan_date: str = Field(max_length=255, index=True)
+    start_date: str | None = Field(default=None, max_length=255, index=True)
+    end_date: str | None = Field(default=None, max_length=255, index=True)
+    business_type: str = Field(max_length=255, index=True)
+    product: str = Field(max_length=255, index=True)
+    specification: str | None = Field(default=None, max_length=255)
+    planned_quantity: float = Field()
+    vehicle_no: str | None = Field(default="", max_length=255)
+    customer_name: str | None = Field(default=None, max_length=255)
+    remarks: str | None = Field(default=None)
+    is_deleted: int = Field(default=0, index=True)
+    execution_status: str | None = Field(default=None, max_length=50, index=True)
+    executed_at: str | None = Field(default=None, max_length=255)
+    last_weighing_slip_id: int | None = Field(default=None)
+    executed_quantity: float | None = Field(default=0)
+    created_at: str = Field(max_length=255)
+    auto_generate_enabled: int = Field(default=0, index=True)
+    auto_generate_end_month: str | None = Field(default=None, max_length=7, index=True)
+    auto_generate_last_month: str | None = Field(default=None, max_length=7)
 
 
 @dataclass

+ 1 - 1
backend/.dibang-backend.pid

@@ -1 +1 @@
-59010
+54943

+ 0 - 2
mobile-h5/src/views/dashboard/index.vue

@@ -42,7 +42,6 @@
 import { ref, computed, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useUserStore } from '@/store/modules/user'
-import dayjs from 'dayjs'
 import { showDialog } from 'vant'
 import { dashboardApi, type DashboardCargoStat, type DashboardPartnerCargoStat } from '@/api/dashboardApi'
 import DualBarChart from '@/components/Charts/DualBarChart.vue'
@@ -50,7 +49,6 @@ import DualBarChart from '@/components/Charts/DualBarChart.vue'
 const router = useRouter()
 const userStore = useUserStore()
 
-const today = dayjs().format('YYYY-MM-DD')
 const isLoading = ref(false)
 const inboundByCargo = ref<DashboardCargoStat[]>([])
 const outboundByCargo = ref<DashboardCargoStat[]>([])

+ 40 - 9
mobile-h5/src/views/inventory/detail.vue

@@ -147,13 +147,24 @@
     </van-popup>
 
     <!-- 货物选择器 -->
-    <van-popup v-model:show="showCargoPicker" round position="bottom">
-      <van-picker
-        :columns="cargoOptions"
-        show-toolbar
-        @confirm="onCargoConfirm"
-        @cancel="showCargoPicker = false"
-      />
+    <van-popup v-model:show="showCargoPicker" round position="bottom" :style="{ maxHeight: '70vh' }">
+      <div class="company-picker-container">
+        <div class="search-container">
+          <van-search
+            v-model="cargoSearchKeyword"
+            placeholder="搜索产品名称"
+            @search="handleCargoSearch"
+            @clear="handleCargoSearch"
+            shape="round"
+          />
+        </div>
+        <van-picker
+          :columns="filteredCargoOptions"
+          show-toolbar
+          @confirm="onCargoConfirm"
+          @cancel="showCargoPicker = false"
+        />
+      </div>
     </van-popup>
 
     <!-- 日期选择器 -->
@@ -237,7 +248,6 @@ const showManualVehicleInput = ref(false)
 const showDateCalendar = ref(false)
 const dateSelected = ref<Date | null>(null)
 const companyOptions = ref<{ text: string; value: string }[]>([])
-const cargoOptions = ref<{ text: string; value: string }[]>([])
 const vehicleOptions = ref<{ vehicleNo: string }[]>([])
 const vehicleSearchKeyword = ref('')
 const vehicleLoading = ref(false)
@@ -256,6 +266,17 @@ const filteredCompanyOptions = computed(() => {
   return [filtered]
 })
 
+// 产品搜索相关
+const cargoSearchKeyword = ref('')
+const allCargoOptions = ref<{ text: string; value: string }[]>([])
+const filteredCargoOptions = computed(() => {
+  if (!cargoSearchKeyword.value) return [allCargoOptions.value]
+  const filtered = allCargoOptions.value.filter(item =>
+    item.text.toLowerCase().includes(cargoSearchKeyword.value.toLowerCase())
+  )
+  return [filtered]
+})
+
 const filteredVehicleOptions = computed(() => {
   if (!vehicleSearchKeyword.value) return vehicleOptions.value
   const kw = vehicleSearchKeyword.value.toLowerCase()
@@ -303,7 +324,7 @@ const handleCompanySearch = () => {
 const loadCargos = async () => {
   try {
     const cargos = await cargoApi.list()
-    cargoOptions.value = cargos.map(item => ({
+    allCargoOptions.value = cargos.map(item => ({
       text: item.name,
       value: item.name
     }))
@@ -312,6 +333,10 @@ const loadCargos = async () => {
   }
 }
 
+const handleCargoSearch = () => {
+  // 触发 computed 过滤
+}
+
 const loadVehicles = async () => {
   vehicleLoading.value = true
   try {
@@ -446,6 +471,12 @@ watch(showCompanyPicker, (newVal) => {
   }
 })
 
+watch(showCargoPicker, (newVal) => {
+  if (newVal) {
+    cargoSearchKeyword.value = ''
+  }
+})
+
 watch(showVehiclePicker, (newVal) => {
   if (newVal) {
     vehicleSearchKeyword.value = ''

+ 104 - 17
mobile-h5/src/views/plans/detail.vue

@@ -126,23 +126,45 @@
     </div>
 
     <!-- 公司选择器 -->
-    <van-popup v-model:show="showCompanyPicker" round position="bottom">
-      <van-picker
-        :columns="companyOptions"
-        show-toolbar
-        @confirm="onCompanyConfirm"
-        @cancel="showCompanyPicker = false"
-      />
+    <van-popup v-model:show="showCompanyPicker" round position="bottom" :style="{ maxHeight: '70vh' }">
+      <div class="company-picker-container">
+        <div class="search-container">
+          <van-search
+            v-model="companySearchKeyword"
+            placeholder="搜索往来单位名称"
+            @search="handleCompanySearch"
+            @clear="handleCompanySearch"
+            shape="round"
+          />
+        </div>
+        <van-picker
+          :columns="filteredCompanyOptions"
+          show-toolbar
+          @confirm="onCompanyConfirm"
+          @cancel="showCompanyPicker = false"
+        />
+      </div>
     </van-popup>
 
     <!-- 货物选择器 -->
-    <van-popup v-model:show="showCargoPicker" round position="bottom">
-      <van-picker
-        :columns="cargoOptions"
-        show-toolbar
-        @confirm="onCargoConfirm"
-        @cancel="showCargoPicker = false"
-      />
+    <van-popup v-model:show="showCargoPicker" round position="bottom" :style="{ maxHeight: '70vh' }">
+      <div class="company-picker-container">
+        <div class="search-container">
+          <van-search
+            v-model="cargoSearchKeyword"
+            placeholder="搜索产品名称"
+            @search="handleCargoSearch"
+            @clear="handleCargoSearch"
+            shape="round"
+          />
+        </div>
+        <van-picker
+          :columns="filteredCargoOptions"
+          show-toolbar
+          @confirm="onCargoConfirm"
+          @cancel="showCargoPicker = false"
+        />
+      </div>
     </van-popup>
 
     <!-- 开始日期选择器 -->
@@ -162,7 +184,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, onMounted } from 'vue'
+import { ref, reactive, computed, onMounted, watch } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { showToast } from 'vant'
 import { planApi, type Plan, type PlanCreate } from '@/api/plansApi'
@@ -187,6 +209,28 @@ const endDateSelected = ref<Date | null>(null)
 const companyOptions = ref<{ text: string; value: string }[]>([])
 const cargoOptions = ref<{ text: string; value: string }[]>([])
 
+// 搜索相关 - 往来单位
+const companySearchKeyword = ref('')
+const allCompanyOptions = ref<{ text: string; value: string }[]>([])
+const filteredCompanyOptions = computed(() => {
+  if (!companySearchKeyword.value) return [allCompanyOptions.value]
+  const filtered = allCompanyOptions.value.filter(item =>
+    item.text.toLowerCase().includes(companySearchKeyword.value.toLowerCase())
+  )
+  return [filtered]
+})
+
+// 搜索相关 - 产品
+const cargoSearchKeyword = ref('')
+const allCargoOptions = ref<{ text: string; value: string }[]>([])
+const filteredCargoOptions = computed(() => {
+  if (!cargoSearchKeyword.value) return [allCargoOptions.value]
+  const filtered = allCargoOptions.value.filter(item =>
+    item.text.toLowerCase().includes(cargoSearchKeyword.value.toLowerCase())
+  )
+  return [filtered]
+})
+
 const formData = reactive<PlanCreate>({
   planNo: '',
   planDate: dayjs().format('YYYY-MM-DD'),
@@ -205,10 +249,11 @@ startDateSelected.value = dayjs().toDate()
 const loadCompanies = async () => {
   try {
     const companies = await companyApi.list()
-    companyOptions.value = companies.map(item => ({
+    allCompanyOptions.value = companies.map(item => ({
       text: item.name,
       value: item.name
     }))
+    companyOptions.value = allCompanyOptions.value
   } catch (error) {
     console.error('加载公司列表失败', error)
   }
@@ -217,10 +262,11 @@ const loadCompanies = async () => {
 const loadCargos = async () => {
   try {
     const cargos = await cargoApi.list()
-    cargoOptions.value = cargos.map(item => ({
+    allCargoOptions.value = cargos.map(item => ({
       text: item.name,
       value: item.name
     }))
+    cargoOptions.value = allCargoOptions.value
   } catch (error) {
     console.error('加载货物列表失败', error)
   }
@@ -236,6 +282,14 @@ const onCargoConfirm = ({ selectedOptions }: any) => {
   showCargoPicker.value = false
 }
 
+const handleCompanySearch = () => {
+  // 触发 computed 过滤
+}
+
+const handleCargoSearch = () => {
+  // 触发 computed 过滤
+}
+
 const onStartDateConfirm = (value: Date | Date[]) => {
   const date = Array.isArray(value) ? value[0] : value
   formData.startDate = dayjs(date).format('YYYY-MM-DD')
@@ -313,6 +367,14 @@ onMounted(async () => {
     await loadData()
   }
 })
+
+// 打开弹窗时重置搜索关键词
+watch(showCompanyPicker, (val) => {
+  if (val) companySearchKeyword.value = ''
+})
+watch(showCargoPicker, (val) => {
+  if (val) cargoSearchKeyword.value = ''
+})
 </script>
 
 <style lang="scss" scoped>
@@ -419,5 +481,30 @@ onMounted(async () => {
   margin-top: 24px;
   padding: 0 16px;
 }
+
+.company-picker-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.search-container {
+  padding: 16px;
+  border-bottom: 1px solid #ebedf0;
+  background: #fff;
+
+  :deep(.van-search) {
+    padding: 0;
+  }
+
+  :deep(.van-search__content) {
+    background: #f7f8fa;
+    border-radius: 20px;
+  }
+
+  :deep(.van-field__control) {
+    font-size: 14px;
+  }
+}
 </style>