Browse Source
feat(order): 新增PO订单管理页面
feat(order): 新增PO订单管理页面
- 实现PO订单查询功能,支持按客户、采购员、订单号、SKU、状态筛选 - 创建表格组件显示PO订单详细信息,包括供应商、商品类别、数量金额等字段 - 集成编辑模式功能,支持下拉选择、数值输入等不同类型的单元格编辑 - 添加分页功能支持,配置多种页面大小选项 - 实现列配置管理,支持动态显示隐藏表格列 - 集成模拟数据源,预设多个PO订单样本数据用于测试 - 添加响应式布局适配不同屏幕尺寸master
4 changed files with 1520 additions and 0 deletions
-
12src/api/quickQuery/quickQueryField.js
-
1193src/views/modules/order/poOrder.vue
-
254src/views/modules/quickQuery/components/QuickQueryFilter.vue
-
61src/views/modules/quickQuery/utils/quickQueryMatch.js
@ -0,0 +1,12 @@ |
|||||
|
import { createAPI } from '@/utils/httpRequest.js' |
||||
|
|
||||
|
// 快捷查询字段配置 - rqrq
|
||||
|
|
||||
|
export const listQuickQueryFieldsByTableId = data => |
||||
|
createAPI('/quickQuery/field/listByTableId', 'post', data) |
||||
|
|
||||
|
export const saveQuickQueryField = data => |
||||
|
createAPI('/quickQuery/field/save', 'post', data) |
||||
|
|
||||
|
export const deleteQuickQueryField = data => |
||||
|
createAPI('/quickQuery/field/delete', 'post', data) |
||||
1193
src/views/modules/order/poOrder.vue
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,254 @@ |
|||||
|
<template> |
||||
|
<span class="quick-query-filter-wrap"> |
||||
|
<el-button type="primary" @click="openDialog">{{ buttonText }}</el-button> |
||||
|
<el-dialog |
||||
|
:title="dialogTitle" |
||||
|
:visible.sync="visible" |
||||
|
:close-on-click-modal="false" |
||||
|
v-drag |
||||
|
width="720px" |
||||
|
@open="onDialogOpen"> |
||||
|
<div style="margin-bottom: 10px;"> |
||||
|
<el-button type="primary" size="small" @click="addRow">新增</el-button> |
||||
|
</div> |
||||
|
<el-table :data="rows" border size="small" max-height="360"> |
||||
|
<el-table-column label="字段" min-width="160" header-align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-select |
||||
|
v-model="scope.row.fieldName" |
||||
|
placeholder="选择字段" |
||||
|
size="mini" |
||||
|
style="width: 100%" |
||||
|
filterable |
||||
|
@change="onFieldChange(scope.row)"> |
||||
|
<el-option |
||||
|
v-for="f in fieldMetaList" |
||||
|
:key="f.fieldName" |
||||
|
:label="f.fieldLabel" |
||||
|
:value="f.fieldName"> |
||||
|
</el-option> |
||||
|
</el-select> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="条件" min-width="120" header-align="center" align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-select |
||||
|
v-model="scope.row.operator" |
||||
|
placeholder="条件" |
||||
|
size="mini" |
||||
|
style="width: 100%" |
||||
|
:disabled="!scope.row.dataType"> |
||||
|
<el-option |
||||
|
v-for="op in operatorsForRow(scope.row)" |
||||
|
:key="op.value" |
||||
|
:label="op.label" |
||||
|
:value="op.value"> |
||||
|
</el-option> |
||||
|
</el-select> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="值" min-width="200" header-align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-date-picker |
||||
|
v-if="scope.row.dataType === 'DATETIME'" |
||||
|
v-model="scope.row.value" |
||||
|
type="date" |
||||
|
size="mini" |
||||
|
style="width: 100%" |
||||
|
value-format="yyyy-MM-dd" |
||||
|
placeholder="选择日期"> |
||||
|
</el-date-picker> |
||||
|
<el-input |
||||
|
v-else-if="scope.row.dataType === 'NUMBER'" |
||||
|
v-model="scope.row.value" |
||||
|
type="number" |
||||
|
size="mini" |
||||
|
placeholder="数字"> |
||||
|
</el-input> |
||||
|
<el-input |
||||
|
v-else |
||||
|
v-model="scope.row.value" |
||||
|
size="mini" |
||||
|
placeholder="文本" |
||||
|
:disabled="!scope.row.fieldName"> |
||||
|
</el-input> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="操作" width="70" header-align="center" align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-button type="text" size="small" @click="removeRow(scope.$index)">删</el-button> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
<span slot="footer" class="dialog-footer"> |
||||
|
<el-button type="primary" @click="apply" :loading="applyLoading">应用</el-button> |
||||
|
<el-button @click="visible = false">取消</el-button> |
||||
|
</span> |
||||
|
</el-dialog> |
||||
|
</span> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { listQuickQueryFieldsByTableId } from '@/api/quickQuery/quickQueryField.js' |
||||
|
|
||||
|
/** |
||||
|
* 快捷查询:根据 tableId 拉取后端可查询字段,拼条件 JSON 字符串交给业务回调 - rqrq |
||||
|
* |
||||
|
* @prop {String} tableId - 与库表 srm_quick_query_field.table_id 一致 |
||||
|
* @prop {String} site - 可选,工厂;与库 site 匹配(含全局 site 空串) |
||||
|
* @prop {Function} applyHandler - (conditionJsonString) => void,入参为条件 JSON 数组字符串 |
||||
|
*/ |
||||
|
export default { |
||||
|
name: 'QuickQueryFilter', |
||||
|
props: { |
||||
|
tableId: { |
||||
|
type: String, |
||||
|
required: true |
||||
|
}, |
||||
|
site: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
buttonText: { |
||||
|
type: String, |
||||
|
default: '快捷查询' |
||||
|
}, |
||||
|
dialogTitle: { |
||||
|
type: String, |
||||
|
default: '快捷查询' |
||||
|
}, |
||||
|
applyHandler: { |
||||
|
type: Function, |
||||
|
required: true |
||||
|
} |
||||
|
}, |
||||
|
data () { |
||||
|
return { |
||||
|
visible: false, |
||||
|
loadingMeta: false, |
||||
|
fieldMetaList: [], |
||||
|
rows: [], |
||||
|
applyLoading: false |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
openDialog () { |
||||
|
this.visible = true |
||||
|
}, |
||||
|
onDialogOpen () { |
||||
|
this.loadFieldMeta() |
||||
|
}, |
||||
|
loadFieldMeta () { |
||||
|
this.loadingMeta = true |
||||
|
listQuickQueryFieldsByTableId({ |
||||
|
tableId: this.tableId, |
||||
|
site: this.site || '' |
||||
|
}).then(({ data }) => { |
||||
|
if (data && data.code === 0) { |
||||
|
this.fieldMetaList = data.rows || [] |
||||
|
if (!this.fieldMetaList.length) { |
||||
|
this.$message.warning('未配置可查询字段,请在库表 srm_quick_query_field 中维护') |
||||
|
} |
||||
|
} else { |
||||
|
this.$alert((data && data.msg) || '加载字段失败', '错误', { confirmButtonText: '确定' }) |
||||
|
} |
||||
|
}).catch(() => { |
||||
|
this.$message.error('加载字段失败') |
||||
|
}).finally(() => { |
||||
|
this.loadingMeta = false |
||||
|
}) |
||||
|
}, |
||||
|
addRow () { |
||||
|
this.rows.push({ |
||||
|
_key: `${Date.now()}_${Math.random()}`, |
||||
|
fieldName: '', |
||||
|
fieldLabel: '', |
||||
|
dataType: '', |
||||
|
operator: '', |
||||
|
value: '' |
||||
|
}) |
||||
|
}, |
||||
|
removeRow (index) { |
||||
|
this.rows.splice(index, 1) |
||||
|
}, |
||||
|
onFieldChange (row) { |
||||
|
const meta = this.fieldMetaList.find(f => f.fieldName === row.fieldName) |
||||
|
if (meta) { |
||||
|
row.fieldLabel = meta.fieldLabel |
||||
|
row.dataType = (meta.dataType || '').toUpperCase() |
||||
|
} else { |
||||
|
row.fieldLabel = '' |
||||
|
row.dataType = '' |
||||
|
} |
||||
|
row.operator = '' |
||||
|
row.value = '' |
||||
|
}, |
||||
|
operatorsForRow (row) { |
||||
|
const t = (row.dataType || '').toUpperCase() |
||||
|
if (t === 'NUMBER') { |
||||
|
return [ |
||||
|
{ label: '大于', value: 'GT' }, |
||||
|
{ label: '小于', value: 'LT' }, |
||||
|
{ label: '等于', value: 'EQ' } |
||||
|
] |
||||
|
} |
||||
|
if (t === 'TEXT') { |
||||
|
return [ |
||||
|
{ label: '等于', value: 'EQ' }, |
||||
|
{ label: '包含', value: 'CONTAINS' } |
||||
|
] |
||||
|
} |
||||
|
if (t === 'DATETIME') { |
||||
|
return [ |
||||
|
{ label: '大于', value: 'GT' }, |
||||
|
{ label: '小于', value: 'LT' } |
||||
|
] |
||||
|
} |
||||
|
return [] |
||||
|
}, |
||||
|
apply () { |
||||
|
const built = [] |
||||
|
for (const r of this.rows) { |
||||
|
if (!r.fieldName) continue |
||||
|
if (!r.operator) { |
||||
|
this.$message.warning(`请为字段「${r.fieldLabel || r.fieldName}」选择条件`) |
||||
|
return |
||||
|
} |
||||
|
if (r.value === '' || r.value === null || r.value === undefined) { |
||||
|
this.$message.warning(`请填写「${r.fieldLabel || r.fieldName}」的值`) |
||||
|
return |
||||
|
} |
||||
|
built.push({ |
||||
|
fieldName: r.fieldName, |
||||
|
fieldLabel: r.fieldLabel, |
||||
|
dataType: r.dataType, |
||||
|
operator: r.operator, |
||||
|
value: r.value |
||||
|
}) |
||||
|
} |
||||
|
if (!built.length) { |
||||
|
this.$message.warning('请至少新增一行完整条件') |
||||
|
return |
||||
|
} |
||||
|
const jsonStr = JSON.stringify(built) |
||||
|
this.applyLoading = true |
||||
|
try { |
||||
|
if (typeof this.applyHandler === 'function') { |
||||
|
this.applyHandler(jsonStr, built) |
||||
|
} |
||||
|
this.$emit('apply', jsonStr, built) |
||||
|
this.visible = false |
||||
|
} finally { |
||||
|
this.applyLoading = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.quick-query-filter-wrap { |
||||
|
display: inline-block; |
||||
|
margin-left: 6px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* 快捷查询条件:解析 JSON + 单行数据是否满足(纯函数,供列表页与后端约定一致) - rqrq |
||||
|
* |
||||
|
* 条件项结构:{ fieldName, fieldLabel?, dataType, operator, value } |
||||
|
* operator: GT | LT | EQ | CONTAINS |
||||
|
* dataType: NUMBER | TEXT | DATETIME |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* @param {string} conditionJsonString - QuickQueryFilter 提交的 JSON 字符串 |
||||
|
* @returns {{ ok: true, conditions: Array } | { ok: false, error: string }} |
||||
|
*/ |
||||
|
export function parseQuickQueryJson (conditionJsonString) { |
||||
|
if (conditionJsonString == null || conditionJsonString === '') { |
||||
|
return { ok: true, conditions: [] } |
||||
|
} |
||||
|
try { |
||||
|
const parsed = JSON.parse(conditionJsonString) |
||||
|
if (!Array.isArray(parsed)) { |
||||
|
return { ok: false, error: '条件必须是数组' } |
||||
|
} |
||||
|
return { ok: true, conditions: parsed } |
||||
|
} catch (e) { |
||||
|
return { ok: false, error: '条件格式错误' } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断一行数据是否满足全部快捷条件(AND) |
||||
|
* @param {Object} row - 行数据 |
||||
|
* @param {Array|null|undefined} conditions - parseQuickQueryJson 得到的 conditions;null/空则恒为 true |
||||
|
* @returns {boolean} |
||||
|
*/ |
||||
|
export function matchQuickQueryRow (row, conditions) { |
||||
|
if (!conditions || !conditions.length) return true |
||||
|
for (const c of conditions) { |
||||
|
const raw = row[c.fieldName] |
||||
|
const target = c.value |
||||
|
const op = c.operator |
||||
|
const dt = (c.dataType || '').toUpperCase() |
||||
|
if (dt === 'TEXT') { |
||||
|
const sv = String(raw == null ? '' : raw) |
||||
|
const tv = String(target == null ? '' : target) |
||||
|
if (op === 'EQ' && sv !== tv) return false |
||||
|
if (op === 'CONTAINS' && !sv.toLowerCase().includes(tv.toLowerCase())) return false |
||||
|
} else if (dt === 'NUMBER') { |
||||
|
const nv = parseFloat(raw) |
||||
|
const nt = parseFloat(target) |
||||
|
if (Number.isNaN(nv) || Number.isNaN(nt)) return false |
||||
|
if (op === 'GT' && !(nv > nt)) return false |
||||
|
if (op === 'LT' && !(nv < nt)) return false |
||||
|
if (op === 'EQ' && nv !== nt) return false |
||||
|
} else if (dt === 'DATETIME') { |
||||
|
const dv = String(raw == null ? '' : raw).slice(0, 10) |
||||
|
const dtarget = String(target == null ? '' : target).slice(0, 10) |
||||
|
if (op === 'GT' && !(dv > dtarget)) return false |
||||
|
if (op === 'LT' && !(dv < dtarget)) return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue