feat: 前后端全量开发 v0.2
后端: - 客户模块 CRUD+搜索 (api/services/repositories 分层) - 合同模块: FG单号规则/意向单转正式单/订单创建自动快照客户信息 - 优化下料: FFD首递减装箱算法(利用率统计/余料/未排入清单) 前端 (Vue3+TS+Element Plus): - 7页面: 登录/工作台/订单中心/合同/客户/样板库/优化下料 - 自研二维窗型画布渲染器: 解析drawing_data渲染玻璃区域/外框/ 扇开向标记/尺寸标注, 支持缩放平移复位 - 样板库与订单联动(用此样板下单自动带图纸) - axios统一envelope拦截/登录守卫/付费墙提示 工程: - SPA history 回退路由, 构建产物挂载后端 - 全接口回归 6/6 通过
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<div class="toolbar">
|
||||
<el-select v-model="catelogId" placeholder="全部分类" clearable style="width:160px" @change="load">
|
||||
<el-option v-for="c in catelogs" :key="c.id" :label="c.name" :value="c.id" />
|
||||
</el-select>
|
||||
<el-input v-model="name" placeholder="模板名称" clearable style="width:200px" @keyup.enter="load" />
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<div style="flex:1" />
|
||||
<span class="count">共 {{ total }} 套官方样板</span>
|
||||
</div>
|
||||
<div v-loading="loading" class="grid">
|
||||
<div v-for="t in rows" :key="t.id" class="item" @click="preview(t)">
|
||||
<el-image :src="t.pic_url" fit="contain" lazy class="pic" />
|
||||
<div class="cap">{{ t.name || `样板#${t.id}` }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination style="margin-top:12px; justify-content:center" background layout="total, prev, pager, next"
|
||||
:total="total" :page-size="page_size" v-model:current-page="page" @current-change="load" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="previewVisible" :title="current?.name || `样板#${current?.id}`" width="72%" top="4vh">
|
||||
<div v-if="current" class="preview">
|
||||
<div class="canvas-box">
|
||||
<WindoorCanvas :drawing-data="current.drawing_data" />
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span>尺寸限制: {{ current.low_width }}-{{ current.high_width }} × {{ current.low_height }}-{{ current.high_height }} mm</span>
|
||||
<span>引用次数: {{ current.times }}</span>
|
||||
<el-button type="primary" size="small" @click="useInOrder">用此样板下单</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { api } from '../api'
|
||||
import WindoorCanvas from '../components/WindoorCanvas.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const page_size = 48
|
||||
const name = ref('')
|
||||
const catelogId = ref<number | undefined>()
|
||||
const catelogs = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const previewVisible = ref(false)
|
||||
const current = ref<any>(null)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const qs = `page=${page.value}&page_size=${page_size}&name=${name.value}${catelogId.value ? `&catelog_id=${catelogId.value}` : ''}`
|
||||
const d = await api<any>('get', `/api/windoor/templates?${qs}`)
|
||||
rows.value = d
|
||||
// 接口无分页元数据时按返回量估计
|
||||
total.value = d.length === page_size ? (page.value - 1) * page_size + d.length + 1 : (page.value - 1) * page_size + d.length
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function preview(t: any) {
|
||||
current.value = await api<any>('get', `/api/windoor/templates/${t.id}`)
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
function useInOrder() {
|
||||
router.push({ path: '/orders', query: { template: current.value.id } })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const tree = await api<any[]>('get', '/api/windoor/catelogs')
|
||||
catelogs.value = tree.flatMap(t => [t, ...((t.children ?? []) as any[])])
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toolbar { display: flex; gap: 10px; margin-bottom: 12px; align-items: center; }
|
||||
.count { color: #909399; font-size: 13px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; min-height: 200px; }
|
||||
.item { border: 1px solid #e4e7ed; border-radius: 8px; padding: 8px; cursor: pointer; background: #fff;
|
||||
transition: box-shadow .2s; display: flex; flex-direction: column; align-items: center; }
|
||||
.item:hover { box-shadow: 0 2px 12px rgba(0,0,0,.12); }
|
||||
.pic { width: 100%; height: 110px; }
|
||||
.cap { font-size: 12px; color: #606266; margin-top: 6px; text-align: center; }
|
||||
.preview .canvas-box { height: 60vh; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; }
|
||||
.preview .meta { display: flex; gap: 20px; margin-top: 10px; color: #606266; font-size: 13px; align-items: center; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user