feat(wms): 添加智能库存助手功能
- 集成 OpenAI 模型配置,支持自然语言库存操作 - 在移动端界面添加智能助手交互组件 - 实现库存操作计划生成与执行流程 - 添加入库登记的修改和删除功能 - 新增 agent 相关 API 接口和数据模型 - 支持上下文关联和连续操作处理 - 提供操作预览和确认机制 - 完善前后端交互和状态管理逻辑
This commit is contained in:
parent
79bbf60817
commit
7737fda061
@ -0,0 +1,32 @@
|
||||
package com.ruoyi.web.controller.warehouse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentAnalyzeRequest;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentExecuteRequest;
|
||||
import com.ruoyi.warehouse.service.IAgentService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/warehouse/agent")
|
||||
public class AgentController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IAgentService agentService;
|
||||
|
||||
@PostMapping("/analyze")
|
||||
public AjaxResult analyze(@RequestBody AgentAnalyzeRequest request)
|
||||
{
|
||||
return success(agentService.analyze(request.getText(), request.getContextResults(), getUsername()));
|
||||
}
|
||||
|
||||
@PostMapping("/execute")
|
||||
public AjaxResult execute(@RequestBody AgentExecuteRequest request)
|
||||
{
|
||||
return success(agentService.execute(request.getPlan(), getUsername()));
|
||||
}
|
||||
}
|
||||
@ -142,3 +142,10 @@ xss:
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
|
||||
# WMS agent model config. Keep api-key in environment variables, not in source code.
|
||||
agent:
|
||||
openai:
|
||||
base-url: ${OPENAI_BASE_URL:http://192.168.28.10:18080}
|
||||
api-key: ${OPENAI_API_KEY:sk-dd185946191d42eaa54892dec488bf7b54cad2d7433aee8aac7a017365b0829d}
|
||||
model: ${OPENAI_MODEL:gpt-5.5}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AgentAnalyzeRequest
|
||||
{
|
||||
private String text;
|
||||
private List<AgentResultItem> contextResults = new ArrayList<>();
|
||||
|
||||
public String getText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public List<AgentResultItem> getContextResults()
|
||||
{
|
||||
return contextResults;
|
||||
}
|
||||
|
||||
public void setContextResults(List<AgentResultItem> contextResults)
|
||||
{
|
||||
this.contextResults = contextResults;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
public class AgentCandidate
|
||||
{
|
||||
private Long id;
|
||||
private String type;
|
||||
private String name;
|
||||
private String detail;
|
||||
|
||||
public AgentCandidate()
|
||||
{
|
||||
}
|
||||
|
||||
public AgentCandidate(Long id, String type, String name, String detail)
|
||||
{
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDetail()
|
||||
{
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(String detail)
|
||||
{
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
public class AgentExecuteRequest
|
||||
{
|
||||
private AgentPlan plan;
|
||||
|
||||
public AgentPlan getPlan()
|
||||
{
|
||||
return plan;
|
||||
}
|
||||
|
||||
public void setPlan(AgentPlan plan)
|
||||
{
|
||||
this.plan = plan;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,317 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AgentPlan
|
||||
{
|
||||
private String action;
|
||||
private String summary;
|
||||
private String message;
|
||||
private boolean executable;
|
||||
private boolean needConfirm;
|
||||
private Long areaId;
|
||||
private Long productId;
|
||||
private Long stockId;
|
||||
private Long recordId;
|
||||
private Integer referenceIndex;
|
||||
private String areaName;
|
||||
private String productName;
|
||||
private String brand;
|
||||
private String category;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal quantity;
|
||||
private Integer inType;
|
||||
private Integer outType;
|
||||
private String operatorName;
|
||||
private String recordNo;
|
||||
private String remark;
|
||||
private BigDecimal currentStock;
|
||||
private BigDecimal afterStock;
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private List<AgentCandidate> candidates = new ArrayList<>();
|
||||
private List<AgentResultItem> results = new ArrayList<>();
|
||||
private AgentPlan nextPlan;
|
||||
|
||||
public String getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getSummary()
|
||||
{
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary)
|
||||
{
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public boolean isExecutable()
|
||||
{
|
||||
return executable;
|
||||
}
|
||||
|
||||
public void setExecutable(boolean executable)
|
||||
{
|
||||
this.executable = executable;
|
||||
}
|
||||
|
||||
public boolean isNeedConfirm()
|
||||
{
|
||||
return needConfirm;
|
||||
}
|
||||
|
||||
public void setNeedConfirm(boolean needConfirm)
|
||||
{
|
||||
this.needConfirm = needConfirm;
|
||||
}
|
||||
|
||||
public Long getAreaId()
|
||||
{
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId)
|
||||
{
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public Long getProductId()
|
||||
{
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId)
|
||||
{
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public Long getStockId()
|
||||
{
|
||||
return stockId;
|
||||
}
|
||||
|
||||
public void setStockId(Long stockId)
|
||||
{
|
||||
this.stockId = stockId;
|
||||
}
|
||||
|
||||
public Long getRecordId()
|
||||
{
|
||||
return recordId;
|
||||
}
|
||||
|
||||
public void setRecordId(Long recordId)
|
||||
{
|
||||
this.recordId = recordId;
|
||||
}
|
||||
|
||||
public Integer getReferenceIndex()
|
||||
{
|
||||
return referenceIndex;
|
||||
}
|
||||
|
||||
public void setReferenceIndex(Integer referenceIndex)
|
||||
{
|
||||
this.referenceIndex = referenceIndex;
|
||||
}
|
||||
|
||||
public String getAreaName()
|
||||
{
|
||||
return areaName;
|
||||
}
|
||||
|
||||
public void setAreaName(String areaName)
|
||||
{
|
||||
this.areaName = areaName;
|
||||
}
|
||||
|
||||
public String getProductName()
|
||||
{
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName)
|
||||
{
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getBrand()
|
||||
{
|
||||
return brand;
|
||||
}
|
||||
|
||||
public void setBrand(String brand)
|
||||
{
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getCategory()
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getSpec()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void setSpec(String spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public String getUnit()
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit)
|
||||
{
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity()
|
||||
{
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity)
|
||||
{
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Integer getInType()
|
||||
{
|
||||
return inType;
|
||||
}
|
||||
|
||||
public void setInType(Integer inType)
|
||||
{
|
||||
this.inType = inType;
|
||||
}
|
||||
|
||||
public Integer getOutType()
|
||||
{
|
||||
return outType;
|
||||
}
|
||||
|
||||
public void setOutType(Integer outType)
|
||||
{
|
||||
this.outType = outType;
|
||||
}
|
||||
|
||||
public String getOperatorName()
|
||||
{
|
||||
return operatorName;
|
||||
}
|
||||
|
||||
public void setOperatorName(String operatorName)
|
||||
{
|
||||
this.operatorName = operatorName;
|
||||
}
|
||||
|
||||
public String getRecordNo()
|
||||
{
|
||||
return recordNo;
|
||||
}
|
||||
|
||||
public void setRecordNo(String recordNo)
|
||||
{
|
||||
this.recordNo = recordNo;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark)
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public BigDecimal getCurrentStock()
|
||||
{
|
||||
return currentStock;
|
||||
}
|
||||
|
||||
public void setCurrentStock(BigDecimal currentStock)
|
||||
{
|
||||
this.currentStock = currentStock;
|
||||
}
|
||||
|
||||
public BigDecimal getAfterStock()
|
||||
{
|
||||
return afterStock;
|
||||
}
|
||||
|
||||
public void setAfterStock(BigDecimal afterStock)
|
||||
{
|
||||
this.afterStock = afterStock;
|
||||
}
|
||||
|
||||
public List<String> getWarnings()
|
||||
{
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public void setWarnings(List<String> warnings)
|
||||
{
|
||||
this.warnings = warnings;
|
||||
}
|
||||
|
||||
public List<AgentCandidate> getCandidates()
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
|
||||
public void setCandidates(List<AgentCandidate> candidates)
|
||||
{
|
||||
this.candidates = candidates;
|
||||
}
|
||||
|
||||
public List<AgentResultItem> getResults()
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
public void setResults(List<AgentResultItem> results)
|
||||
{
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public AgentPlan getNextPlan()
|
||||
{
|
||||
return nextPlan;
|
||||
}
|
||||
|
||||
public void setNextPlan(AgentPlan nextPlan)
|
||||
{
|
||||
this.nextPlan = nextPlan;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AgentResultItem
|
||||
{
|
||||
private Long id;
|
||||
private Long areaId;
|
||||
private Long productId;
|
||||
private String areaName;
|
||||
private String productName;
|
||||
private String brand;
|
||||
private String category;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal stockNum;
|
||||
private String recordNo;
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getAreaId()
|
||||
{
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId)
|
||||
{
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public Long getProductId()
|
||||
{
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId)
|
||||
{
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getAreaName()
|
||||
{
|
||||
return areaName;
|
||||
}
|
||||
|
||||
public void setAreaName(String areaName)
|
||||
{
|
||||
this.areaName = areaName;
|
||||
}
|
||||
|
||||
public String getProductName()
|
||||
{
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName)
|
||||
{
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getBrand()
|
||||
{
|
||||
return brand;
|
||||
}
|
||||
|
||||
public void setBrand(String brand)
|
||||
{
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getCategory()
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getSpec()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void setSpec(String spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public String getUnit()
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit)
|
||||
{
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public BigDecimal getStockNum()
|
||||
{
|
||||
return stockNum;
|
||||
}
|
||||
|
||||
public void setStockNum(BigDecimal stockNum)
|
||||
{
|
||||
this.stockNum = stockNum;
|
||||
}
|
||||
|
||||
public String getRecordNo()
|
||||
{
|
||||
return recordNo;
|
||||
}
|
||||
|
||||
public void setRecordNo(String recordNo)
|
||||
{
|
||||
this.recordNo = recordNo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.ruoyi.warehouse.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentPlan;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentResultItem;
|
||||
|
||||
public interface IAgentService
|
||||
{
|
||||
public AgentPlan analyze(String text, List<AgentResultItem> contextResults, String username);
|
||||
|
||||
public AgentPlan execute(AgentPlan plan, String username);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
21
ruoyi-ui/src/api/warehouse/agent.js
Normal file
21
ruoyi-ui/src/api/warehouse/agent.js
Normal file
@ -0,0 +1,21 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function analyzeAgent(data) {
|
||||
return request({
|
||||
url: '/warehouse/agent/analyze',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
},
|
||||
timeout: 45000
|
||||
})
|
||||
}
|
||||
|
||||
export function executeAgent(data) {
|
||||
return request({
|
||||
url: '/warehouse/agent/execute',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@ -8,6 +8,68 @@
|
||||
<el-button type="primary" icon="el-icon-mobile-phone" @click="$router.push('/mobile')">移动端入口</el-button>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel">
|
||||
<div class="agent-title">
|
||||
<div>
|
||||
<h2>智能库存助手</h2>
|
||||
<p>输入一句话,系统先生成操作草稿,确认后再真正执行。</p>
|
||||
</div>
|
||||
<el-tag size="small" type="info">第一阶段</el-tag>
|
||||
</div>
|
||||
<div class="agent-input">
|
||||
<el-input
|
||||
v-model="agent.text"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
resize="none"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="例如:销售出库 10 个某产品,库区 A,出库人张三"
|
||||
@keyup.enter.native="analyzeAgentText"
|
||||
/>
|
||||
<el-button icon="el-icon-delete" :disabled="!agent.text && !agent.plan" @click="clearAgent">清空</el-button>
|
||||
<el-button type="primary" icon="el-icon-magic-stick" :loading="agent.loading" @click="analyzeAgentText">分析</el-button>
|
||||
</div>
|
||||
<div v-if="agent.plan" class="agent-result">
|
||||
<div class="agent-plan">
|
||||
<div>
|
||||
<el-tag size="small" :type="agent.plan.executable ? 'success' : 'warning'">{{ actionLabel(agent.plan.action) }}</el-tag>
|
||||
<strong>{{ agent.plan.summary || agent.plan.message || '已生成操作草稿' }}</strong>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="agent.plan.needConfirm"
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-check"
|
||||
:disabled="!agent.plan.executable"
|
||||
:loading="agent.executing"
|
||||
@click="executeAgentPlan"
|
||||
>确认执行</el-button>
|
||||
</div>
|
||||
<el-descriptions v-if="planItems.length" :column="3" size="small" border>
|
||||
<el-descriptions-item v-for="item in planItems" :key="item.label" :label="item.label">{{ item.value }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-alert v-if="agent.plan.message" :title="agent.plan.message" type="info" show-icon :closable="false" />
|
||||
<div v-if="agent.plan.warnings && agent.plan.warnings.length" class="agent-warnings">
|
||||
<el-tag v-for="item in agent.plan.warnings" :key="item" type="warning" size="small">{{ item }}</el-tag>
|
||||
</div>
|
||||
<div v-if="agent.plan.candidates && agent.plan.candidates.length" class="agent-candidates">
|
||||
<span v-for="item in agent.plan.candidates" :key="item.type + item.id">{{ item.name }} {{ item.detail || '' }}</span>
|
||||
</div>
|
||||
<el-table v-if="agent.plan.results && agent.plan.results.length" :data="agent.plan.results" size="mini" class="agent-table">
|
||||
<el-table-column type="index" label="#" width="54" />
|
||||
<el-table-column label="库区" prop="areaName" width="120" />
|
||||
<el-table-column label="产品" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
||||
<div class="product-meta">{{ productMeta(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" prop="stockNum" align="right" width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary-grid">
|
||||
<div class="summary-card">
|
||||
<span>库存品项</span>
|
||||
@ -85,6 +147,7 @@ import { listStock } from '@/api/warehouse/stock'
|
||||
import { listInRecord } from '@/api/warehouse/inRecord'
|
||||
import { listOutRecord } from '@/api/warehouse/outRecord'
|
||||
import { listStockCheck } from '@/api/warehouse/stockCheck'
|
||||
import { analyzeAgent, executeAgent } from '@/api/warehouse/agent'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@ -96,13 +159,88 @@ export default {
|
||||
pendingCheck: 0,
|
||||
outTotal: 0
|
||||
},
|
||||
stockList: []
|
||||
stockList: [],
|
||||
agent: {
|
||||
text: '',
|
||||
loading: false,
|
||||
executing: false,
|
||||
contextResults: [],
|
||||
plan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
planItems() {
|
||||
const plan = this.agent.plan
|
||||
if (!plan) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{ label: '库区', value: plan.areaName },
|
||||
{ label: '产品', value: plan.productName },
|
||||
{ label: '品牌', value: plan.brand },
|
||||
{ label: '类别', value: plan.category },
|
||||
{ label: '规格', value: plan.spec },
|
||||
{ label: '单位', value: plan.unit },
|
||||
{ label: '数量', value: plan.quantity },
|
||||
{ label: '操作人', value: plan.operatorName },
|
||||
{ label: '当前库存', value: plan.currentStock },
|
||||
{ label: '预计库存', value: plan.afterStock },
|
||||
{ label: '单号', value: plan.recordNo }
|
||||
].filter(item => item.value !== undefined && item.value !== null && item.value !== '')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDashboard()
|
||||
},
|
||||
methods: {
|
||||
analyzeAgentText() {
|
||||
const text = (this.agent.text || '').trim()
|
||||
if (!text) {
|
||||
this.$modal.msgWarning('请输入要操作的内容')
|
||||
return
|
||||
}
|
||||
this.agent.loading = true
|
||||
analyzeAgent({ text, contextResults: this.agent.contextResults }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.agent.text = ''
|
||||
if (res.data && res.data.results && res.data.results.length) {
|
||||
this.agent.contextResults = res.data.results
|
||||
}
|
||||
}).finally(() => {
|
||||
this.agent.loading = false
|
||||
})
|
||||
},
|
||||
executeAgentPlan() {
|
||||
if (!this.agent.plan || !this.agent.plan.executable) {
|
||||
return
|
||||
}
|
||||
this.agent.executing = true
|
||||
executeAgent({ plan: this.agent.plan }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.$modal.msgSuccess(res.data.message || '操作完成')
|
||||
this.loadDashboard()
|
||||
this.agent.text = ''
|
||||
}).finally(() => {
|
||||
this.agent.executing = false
|
||||
})
|
||||
},
|
||||
clearAgent() {
|
||||
this.agent.text = ''
|
||||
this.agent.plan = null
|
||||
},
|
||||
actionLabel(action) {
|
||||
const map = {
|
||||
query_stock: '查询库存',
|
||||
create_in_record: '入库登记',
|
||||
confirm_in_record: '确认入库',
|
||||
create_out_record: '出库',
|
||||
create_stock_check: '盘库登记',
|
||||
create_product: '新增产品',
|
||||
create_area: '新增库区'
|
||||
}
|
||||
return map[action] || '未识别'
|
||||
},
|
||||
loadDashboard() {
|
||||
listStock({ pageNum: 1, pageSize: 8 }).then(res => {
|
||||
this.stockList = res.rows || []
|
||||
@ -150,6 +288,76 @@ export default {
|
||||
margin: 6px 0 0;
|
||||
color: #7a8794;
|
||||
}
|
||||
.agent-panel {
|
||||
margin-bottom: 16px;
|
||||
padding: 18px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e6ebf2;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.agent-title {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.agent-title h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.agent-title p {
|
||||
margin: 4px 0 0;
|
||||
color: #7a8794;
|
||||
}
|
||||
.agent-input {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 88px 96px;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.agent-input .el-button {
|
||||
height: 54px;
|
||||
}
|
||||
.agent-result {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.agent-plan {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #f7f9fc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.agent-plan strong {
|
||||
margin-left: 8px;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.agent-warnings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-candidates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-candidates span {
|
||||
padding: 6px 10px;
|
||||
color: #606266;
|
||||
background: #f4f6f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.agent-table {
|
||||
width: 100%;
|
||||
}
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@ -222,6 +430,16 @@ export default {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.agent-input {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.agent-input .el-button {
|
||||
height: 40px;
|
||||
}
|
||||
.agent-plan {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.summary-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@ -29,6 +29,63 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-agent">
|
||||
<div class="agent-head">
|
||||
<div>
|
||||
<div class="agent-title">智能库存助手</div>
|
||||
<div class="agent-subtitle">说一句或输入一句,确认后执行</div>
|
||||
</div>
|
||||
<el-tag size="mini" type="info">AI</el-tag>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="agent.text"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
resize="none"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="例如:销售出库 10 个某产品,库区 A,出库人张三"
|
||||
/>
|
||||
<div class="agent-actions">
|
||||
<el-button icon="el-icon-delete" :disabled="!agent.text && !agent.plan" @click="clearAgent">清空</el-button>
|
||||
<el-button type="primary" icon="el-icon-magic-stick" :loading="agent.loading" @click="analyzeAgentText">分析</el-button>
|
||||
</div>
|
||||
<div v-if="agent.plan" class="agent-result">
|
||||
<div class="agent-plan">
|
||||
<div>
|
||||
<el-tag size="mini" :type="agent.plan.executable ? 'success' : 'warning'">{{ actionLabel(agent.plan.action) }}</el-tag>
|
||||
<strong>{{ agent.plan.summary || agent.plan.message || '已生成操作草稿' }}</strong>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="showAgentExecute(agent.plan)"
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-check"
|
||||
:disabled="!agent.plan.executable"
|
||||
:loading="agent.executing"
|
||||
@click="executeAgentPlan"
|
||||
>确认</el-button>
|
||||
</div>
|
||||
<div v-if="agentPlanItems.length" class="agent-detail">
|
||||
<span v-for="item in agentPlanItems" :key="item.label">{{ item.label }}:{{ item.value }}</span>
|
||||
</div>
|
||||
<el-alert v-if="agent.plan.message" :title="agent.plan.message" type="info" show-icon :closable="false" />
|
||||
<div v-if="agent.plan.warnings && agent.plan.warnings.length" class="agent-tags">
|
||||
<el-tag v-for="item in agent.plan.warnings" :key="item" type="warning" size="mini">{{ item }}</el-tag>
|
||||
</div>
|
||||
<div v-if="agent.plan.candidates && agent.plan.candidates.length" class="agent-candidates">
|
||||
<span v-for="item in agent.plan.candidates" :key="item.type + item.id">{{ item.name }} {{ item.detail || '' }}</span>
|
||||
</div>
|
||||
<article v-for="(item, index) in (agent.plan.results || [])" :key="'agent-result-' + item.id" class="agent-stock">
|
||||
<div>
|
||||
<div class="card-title">第 {{ index + 1 }} 项:{{ item.productName || '-' }}</div>
|
||||
<div class="meta-line">{{ item.areaName || '-' }} | {{ productMeta(item) }}</div>
|
||||
</div>
|
||||
<strong>{{ item.stockNum }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-panel">
|
||||
<button @click="openInForm">入库登记</button>
|
||||
<button @click="openOutForm">出库登记</button>
|
||||
@ -108,6 +165,8 @@
|
||||
<span>入库人:{{ item.inOperator || '-' }}</span>
|
||||
</div>
|
||||
<div class="card-actions" v-if="item.inStatus === 0">
|
||||
<el-button type="primary" size="mini" icon="el-icon-edit" @click="openInForm(item)" v-hasPermi="['warehouse:in:edit']">修改</el-button>
|
||||
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteInItem(item)" v-hasPermi="['warehouse:in:remove']">删除</el-button>
|
||||
<el-button type="success" size="mini" icon="el-icon-check" @click="confirmIn(item)" v-hasPermi="['warehouse:in:confirm']">确认入库</el-button>
|
||||
</div>
|
||||
</article>
|
||||
@ -372,9 +431,10 @@ import { listArea, getArea, delArea, addArea, updateArea, optionselectArea } fro
|
||||
import { listProduct, getProduct, delProduct, addProduct, updateProduct, optionselectProduct } from '@/api/warehouse/product'
|
||||
import { optionselectUser } from '@/api/warehouse/user'
|
||||
import { listStock, outStock } from '@/api/warehouse/stock'
|
||||
import { listInRecord, addInRecord, confirmInRecord } from '@/api/warehouse/inRecord'
|
||||
import { listInRecord, getInRecord, addInRecord, updateInRecord, delInRecord, confirmInRecord } from '@/api/warehouse/inRecord'
|
||||
import { listOutRecord, addOutRecord } from '@/api/warehouse/outRecord'
|
||||
import { listStockCheck, addStockCheck, confirmStockCheck } from '@/api/warehouse/stockCheck'
|
||||
import { analyzeAgent, executeAgent } from '@/api/warehouse/agent'
|
||||
|
||||
export default {
|
||||
name: 'MobileWarehouse',
|
||||
@ -402,7 +462,15 @@ export default {
|
||||
dialogType: '',
|
||||
dialogTitle: '',
|
||||
form: {},
|
||||
formRules: {}
|
||||
formRules: {},
|
||||
agent: {
|
||||
text: '',
|
||||
loading: false,
|
||||
executing: false,
|
||||
contextResults: [],
|
||||
pendingNextPlan: null,
|
||||
plan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -415,6 +483,25 @@ export default {
|
||||
},
|
||||
unitOptions() {
|
||||
return this.distinctProductOptions('unit')
|
||||
},
|
||||
agentPlanItems() {
|
||||
const plan = this.agent.plan
|
||||
if (!plan) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{ label: '库区', value: plan.areaName },
|
||||
{ label: '产品', value: plan.productName },
|
||||
{ label: '品牌', value: plan.brand },
|
||||
{ label: '类别', value: plan.category },
|
||||
{ label: '规格', value: plan.spec },
|
||||
{ label: '单位', value: plan.unit },
|
||||
{ label: '数量', value: plan.quantity },
|
||||
{ label: '人员', value: plan.operatorName },
|
||||
{ label: '当前', value: plan.currentStock },
|
||||
{ label: '预计', value: plan.afterStock },
|
||||
{ label: '单号', value: plan.recordNo }
|
||||
].filter(item => item.value !== undefined && item.value !== null && item.value !== '')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -422,6 +509,158 @@ export default {
|
||||
this.refreshAll()
|
||||
},
|
||||
methods: {
|
||||
analyzeAgentText() {
|
||||
const text = (this.agent.text || '').trim()
|
||||
if (!text) {
|
||||
this.$modal.msgWarning('请输入要操作的内容')
|
||||
return
|
||||
}
|
||||
this.agent.loading = true
|
||||
analyzeAgent({ text, contextResults: this.agent.contextResults }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.agent.text = ''
|
||||
if (res.data && res.data.results && res.data.results.length) {
|
||||
this.agent.contextResults = res.data.results
|
||||
}
|
||||
this.autoFillAgentForm(res.data)
|
||||
}).finally(() => {
|
||||
this.agent.loading = false
|
||||
})
|
||||
},
|
||||
executeAgentPlan() {
|
||||
if (!this.agent.plan || !this.agent.plan.executable) {
|
||||
return
|
||||
}
|
||||
this.agent.executing = true
|
||||
executeAgent({ plan: this.agent.plan }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.$modal.msgSuccess(res.data.message || '操作完成')
|
||||
this.refreshAll()
|
||||
this.agent.text = ''
|
||||
}).finally(() => {
|
||||
this.agent.executing = false
|
||||
})
|
||||
},
|
||||
clearAgent() {
|
||||
this.agent.text = ''
|
||||
this.agent.plan = null
|
||||
this.agent.pendingNextPlan = null
|
||||
},
|
||||
autoFillAgentForm(plan) {
|
||||
if (!plan || !plan.executable) {
|
||||
return
|
||||
}
|
||||
if (plan.action === 'create_out_record' && plan.stockId) {
|
||||
this.dialogType = 'stockOut'
|
||||
this.dialogTitle = '库存出库'
|
||||
this.form = {
|
||||
id: plan.stockId,
|
||||
productName: plan.productName,
|
||||
brand: plan.brand,
|
||||
category: plan.category,
|
||||
spec: plan.spec,
|
||||
unit: plan.unit,
|
||||
outOperator: plan.operatorName,
|
||||
outType: plan.outType || 3,
|
||||
outNum: plan.quantity,
|
||||
sourceNo: undefined,
|
||||
remark: plan.remark
|
||||
}
|
||||
this.setOutRules()
|
||||
} else if (plan.action === 'create_out_record') {
|
||||
this.dialogType = 'out'
|
||||
this.dialogTitle = '出库登记'
|
||||
this.form = {
|
||||
areaId: plan.areaId,
|
||||
productId: plan.productId,
|
||||
outOperator: plan.operatorName,
|
||||
outType: plan.outType || 3,
|
||||
outNum: plan.quantity,
|
||||
sourceNo: undefined,
|
||||
remark: plan.remark
|
||||
}
|
||||
this.setOutRules()
|
||||
} else if (plan.action === 'create_in_record') {
|
||||
this.dialogType = 'in'
|
||||
this.dialogTitle = '入库登记'
|
||||
this.form = {
|
||||
areaId: plan.areaId,
|
||||
productId: plan.productId,
|
||||
inOperator: plan.operatorName,
|
||||
inType: plan.inType || 2,
|
||||
inNum: plan.quantity,
|
||||
remark: plan.remark
|
||||
}
|
||||
this.setInRules()
|
||||
} else if (plan.action === 'create_stock_check') {
|
||||
this.dialogType = 'check'
|
||||
this.dialogTitle = '盘库登记'
|
||||
this.form = {
|
||||
areaId: plan.areaId,
|
||||
productId: plan.productId,
|
||||
realNum: plan.quantity,
|
||||
reason: plan.remark
|
||||
}
|
||||
this.setCheckRules()
|
||||
} else if (plan.action === 'create_product') {
|
||||
this.dialogType = 'product'
|
||||
this.dialogTitle = '新增产品'
|
||||
this.agent.pendingNextPlan = plan.nextPlan || null
|
||||
this.form = {
|
||||
id: undefined,
|
||||
productCode: undefined,
|
||||
productName: plan.productName,
|
||||
brand: plan.brand,
|
||||
category: plan.category,
|
||||
spec: plan.spec,
|
||||
unit: plan.unit,
|
||||
remark: plan.remark
|
||||
}
|
||||
this.formRules = {
|
||||
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
|
||||
}
|
||||
} else if (plan.action === 'create_area') {
|
||||
this.dialogType = 'area'
|
||||
this.dialogTitle = '新增库区'
|
||||
this.agent.pendingNextPlan = plan.nextPlan || null
|
||||
this.form = {
|
||||
id: undefined,
|
||||
areaCode: undefined,
|
||||
areaName: plan.areaName,
|
||||
remark: plan.remark
|
||||
}
|
||||
this.formRules = {
|
||||
areaName: [{ required: true, message: '请输入库区名称', trigger: 'blur' }]
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
this.dialogOpen = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.mobileForm) {
|
||||
this.$refs.mobileForm.clearValidate()
|
||||
}
|
||||
})
|
||||
this.$modal.msgSuccess('已自动填好表单,请确认后提交')
|
||||
},
|
||||
showAgentExecute(plan) {
|
||||
if (!plan || !plan.needConfirm) {
|
||||
return false
|
||||
}
|
||||
return !['create_out_record', 'create_in_record', 'create_stock_check', 'create_product', 'create_area'].includes(plan.action)
|
||||
},
|
||||
actionLabel(action) {
|
||||
const map = {
|
||||
query_stock: '查库存',
|
||||
create_in_record: '入库',
|
||||
confirm_in_record: '确认入库',
|
||||
create_out_record: '出库',
|
||||
create_stock_check: '盘库',
|
||||
create_product: '新增产品',
|
||||
create_area: '新增库区'
|
||||
}
|
||||
return map[action] || '未识别'
|
||||
},
|
||||
loadOptions() {
|
||||
optionselectArea().then(res => { this.areaOptions = res.data || [] })
|
||||
optionselectProduct().then(res => { this.productOptions = res.data || [] })
|
||||
@ -489,10 +728,31 @@ export default {
|
||||
const values = this.productOptions.map(item => item[field]).filter(item => item !== undefined && item !== null && item !== '')
|
||||
return Array.from(new Set(values))
|
||||
},
|
||||
openInForm() {
|
||||
openInForm(row) {
|
||||
this.dialogType = 'in'
|
||||
this.dialogTitle = '入库登记'
|
||||
this.form = { areaId: undefined, productId: undefined, inOperator: undefined, inType: 1, inNum: undefined, remark: undefined }
|
||||
this.setInRules()
|
||||
const setForm = data => {
|
||||
this.dialogTitle = data && data.id ? '修改入库登记' : '入库登记'
|
||||
this.form = Object.assign({
|
||||
id: undefined,
|
||||
inNo: undefined,
|
||||
areaId: undefined,
|
||||
productId: undefined,
|
||||
inOperator: undefined,
|
||||
inType: 1,
|
||||
inNum: undefined,
|
||||
remark: undefined
|
||||
}, data || {})
|
||||
this.dialogOpen = true
|
||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||
}
|
||||
if (row && row.id) {
|
||||
getInRecord(row.id).then(response => setForm(response.data))
|
||||
} else {
|
||||
setForm()
|
||||
}
|
||||
},
|
||||
setInRules() {
|
||||
this.formRules = {
|
||||
areaId: [{ required: true, message: '请选择库区', trigger: 'change' }],
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
@ -500,8 +760,6 @@ export default {
|
||||
inType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
inNum: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
||||
}
|
||||
this.dialogOpen = true
|
||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||
},
|
||||
openOutForm() {
|
||||
this.dialogType = 'out'
|
||||
@ -532,13 +790,62 @@ export default {
|
||||
this.dialogType = 'check'
|
||||
this.dialogTitle = '盘库登记'
|
||||
this.form = { areaId: undefined, productId: undefined, realNum: undefined, reason: undefined }
|
||||
this.setCheckRules()
|
||||
this.dialogOpen = true
|
||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||
},
|
||||
setCheckRules() {
|
||||
this.formRules = {
|
||||
areaId: [{ required: true, message: '请选择库区', trigger: 'change' }],
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
realNum: [{ required: true, message: '请输入实盘数量', trigger: 'blur' }]
|
||||
}
|
||||
this.dialogOpen = true
|
||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||
},
|
||||
openPendingAgentPlanAfterProduct() {
|
||||
const nextPlan = this.agent.pendingNextPlan
|
||||
const savedProduct = Object.assign({}, this.form)
|
||||
this.agent.pendingNextPlan = null
|
||||
optionselectProduct().then(res => {
|
||||
this.productOptions = res.data || []
|
||||
const product = this.productOptions.find(item =>
|
||||
this.sameText(item.productName, savedProduct.productName) &&
|
||||
this.sameText(item.brand, savedProduct.brand) &&
|
||||
this.sameText(item.category, savedProduct.category) &&
|
||||
this.sameText(item.spec, savedProduct.spec)
|
||||
)
|
||||
if (!product) {
|
||||
this.$modal.msgWarning('产品已保存,请手动选择产品完成入库')
|
||||
return
|
||||
}
|
||||
nextPlan.productId = product.id
|
||||
nextPlan.productName = product.productName
|
||||
nextPlan.brand = product.brand
|
||||
nextPlan.category = product.category
|
||||
nextPlan.spec = product.spec
|
||||
nextPlan.unit = product.unit
|
||||
nextPlan.executable = true
|
||||
this.$nextTick(() => this.autoFillAgentForm(nextPlan))
|
||||
})
|
||||
},
|
||||
openPendingAgentPlanAfterArea() {
|
||||
const nextPlan = this.agent.pendingNextPlan
|
||||
const savedArea = Object.assign({}, this.form)
|
||||
this.agent.pendingNextPlan = null
|
||||
optionselectArea().then(res => {
|
||||
this.areaOptions = res.data || []
|
||||
const area = this.areaOptions.find(item => this.sameText(item.areaName, savedArea.areaName))
|
||||
if (!area) {
|
||||
this.$modal.msgWarning('库区已保存,请手动选择库区完成操作')
|
||||
return
|
||||
}
|
||||
nextPlan.areaId = area.id
|
||||
nextPlan.areaName = area.areaName
|
||||
nextPlan.executable = true
|
||||
this.$nextTick(() => this.autoFillAgentForm(nextPlan))
|
||||
})
|
||||
},
|
||||
sameText(left, right) {
|
||||
return String(left || '').trim().toLowerCase() === String(right || '').trim().toLowerCase()
|
||||
},
|
||||
openProductForm(row) {
|
||||
this.dialogType = 'product'
|
||||
@ -592,7 +899,7 @@ export default {
|
||||
this.$refs.mobileForm.validate(valid => {
|
||||
if (!valid) return
|
||||
let request
|
||||
if (this.dialogType === 'in') request = addInRecord(this.form)
|
||||
if (this.dialogType === 'in') request = this.form.id ? updateInRecord(this.form) : addInRecord(this.form)
|
||||
if (this.dialogType === 'out') request = addOutRecord(this.form)
|
||||
if (this.dialogType === 'stockOut') request = outStock(this.form)
|
||||
if (this.dialogType === 'check') request = addStockCheck(this.form)
|
||||
@ -600,6 +907,7 @@ export default {
|
||||
if (this.dialogType === 'area') request = this.form.id ? updateArea(this.form) : addArea(this.form)
|
||||
request.then(() => {
|
||||
const editMaintainData = (this.dialogType === 'product' || this.dialogType === 'area') && this.form.id
|
||||
const hasPendingNextPlan = !!this.agent.pendingNextPlan
|
||||
this.$modal.msgSuccess(editMaintainData ? '修改成功' : '保存成功')
|
||||
this.dialogOpen = false
|
||||
if (this.dialogType === 'product') {
|
||||
@ -607,6 +915,11 @@ export default {
|
||||
this.loadOptions()
|
||||
this.loadStock()
|
||||
this.loadSummary()
|
||||
if (hasPendingNextPlan && !editMaintainData) {
|
||||
this.openPendingAgentPlanAfterProduct()
|
||||
} else {
|
||||
this.clearAgent()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.dialogType === 'area') {
|
||||
@ -614,8 +927,14 @@ export default {
|
||||
this.loadOptions()
|
||||
this.loadStock()
|
||||
this.loadSummary()
|
||||
if (hasPendingNextPlan && !editMaintainData) {
|
||||
this.openPendingAgentPlanAfterArea()
|
||||
} else {
|
||||
this.clearAgent()
|
||||
}
|
||||
return
|
||||
}
|
||||
this.clearAgent()
|
||||
this.refreshAll()
|
||||
})
|
||||
})
|
||||
@ -638,6 +957,13 @@ export default {
|
||||
this.loadSummary()
|
||||
}).catch(() => {})
|
||||
},
|
||||
deleteInItem(row) {
|
||||
this.$modal.confirm('确认删除入库记录“' + (row.inNo || '-') + '”?').then(() => delInRecord(row.id)).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.loadInRecords()
|
||||
this.loadSummary()
|
||||
}).catch(() => {})
|
||||
},
|
||||
confirmIn(row) {
|
||||
this.$modal.confirm('确认将该记录入库?').then(() => confirmInRecord(row.id)).then(() => {
|
||||
this.$modal.msgSuccess('入库成功')
|
||||
@ -715,6 +1041,98 @@ export default {
|
||||
.summary-card.danger strong {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.mobile-agent {
|
||||
margin: 12px 0;
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e8edf3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.agent-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.agent-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
}
|
||||
.agent-subtitle {
|
||||
color: #7a8794;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.agent-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 86px 1fr;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.agent-result {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.agent-plan {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
background: #f7f9fc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.agent-plan strong {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.agent-detail {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.agent-detail span {
|
||||
padding: 5px 8px;
|
||||
color: #606266;
|
||||
background: #f4f6f9;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.agent-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.agent-candidates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.agent-candidates span {
|
||||
padding: 5px 8px;
|
||||
color: #606266;
|
||||
background: #f4f6f9;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.agent-stock {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #e8edf3;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.agent-stock strong {
|
||||
color: #1f7aec;
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.quick-panel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@ -874,4 +1292,19 @@ export default {
|
||||
.mobile-dialog .el-dialog__body {
|
||||
padding: 12px 16px 0;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.el-message-box__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.el-message-box {
|
||||
width: 100% !important;
|
||||
max-width: 340px;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user