python-flask和Vue两种模式初模板

This commit is contained in:
dxc
2026-01-26 17:00:12 +08:00
parent ee9f4aed3e
commit 2f8a5c55b1
36 changed files with 943 additions and 126 deletions

View File

@ -1,30 +1,115 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
// 不需要引入组件,由 router-view 控制
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
<div class="app-wrapper">
<header class="app-header">
<div class="logo-container">
<router-link to="/" class="home-link">
<img src="./assets/iris.png" class="logo" alt="Logo" />
<span class="system-title">库存管理系统</span>
</router-link>
</div>
<div class="header-right">
</div>
</header>
<main class="app-content">
<router-view />
</main>
<footer class="app-footer">
<span class="version-tag">
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
当前版本: 1.0 Beta (测试版)
</span>
</footer>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
<style>
/* 全局重置 */
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f5f7fa;
}
#app {
height: 100%;
}
.app-wrapper {
display: flex;
flex-direction: column;
height: 100vh; /* 占满全屏高度 */
}
/* 顶部栏样式 */
.app-header {
height: 60px;
background-color: #ffffff;
border-bottom: 1px solid #dcdfe6;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
flex-shrink: 0; /* 防止被压缩 */
}
.logo-container {
display: flex;
align-items: center;
}
.home-link {
display: flex;
align-items: center;
gap: 15px;
text-decoration: none;
cursor: pointer;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
height: 40px;
width: auto;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
.system-title {
font-size: 18px;
font-weight: bold;
color: #303133;
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
/* 内容区样式 */
.app-content {
flex: 1; /* 关键:这会让内容区自动撑开,把 footer 挤到最底下 */
overflow: auto;
padding: 20px;
}
</style>
/* 新增:底部栏样式 */
.app-footer {
height: 30px; /* 固定高度 */
background-color: #e9e9eb; /* 稍微深一点的灰色,区分内容区 */
border-top: 1px solid #dcdfe6;
display: flex;
align-items: center;
justify-content: center; /* 文字居中 */
flex-shrink: 0; /* 防止被压缩 */
font-size: 12px;
color: #909399;
}
.version-tag {
display: flex;
align-items: center;
font-weight: 500;
color: #e6a23c; /* 使用橙色,表示“测试/警告”意味 */
}
</style>

View File

View File

View File

@ -0,0 +1,39 @@
import request from '@/utils/request'
// 注意baseURL 已经是 '/api/v1' 了,所以这里只需要写剩下的部分
// 获取入库列表
// 最终请求: /api/v1 + /stocks/inbound = /api/v1/stocks/inbound
export function getInboundList(params: any) {
return request({
url: '/stocks/inbound', // <--- 修改点:去掉了 /api/v1
method: 'get',
params
})
}
// 新增入库
export function createInbound(data: any) {
return request({
url: '/stocks/inbound', // <--- 修改点
method: 'post',
data
})
}
// 修改入库
export function updateInbound(id: number, data: any) {
return request({
url: `/stocks/inbound/${id}`, // <--- 修改点
method: 'put',
data
})
}
// 删除入库
export function deleteInbound(id: number) {
return request({
url: `/stocks/inbound/${id}`, // <--- 修改点
method: 'delete'
})
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -1,5 +1,29 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
// 1. 引入路由配置 (确保你已经创建了 src/router/index.ts)
import router from './router'
// 2. 引入 Element Plus (UI组件库)
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 引入中文包
import zhCn from 'element-plus/es/locale/lang/zh-cn'
// 3. 引入图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
// 注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 使用插件
app.use(router)
app.use(ElementPlus, {
locale: zhCn, // 设置为中文
})
app.mount('#app')

View File

@ -0,0 +1,24 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
// --- 修改点:根路径不再重定向,而是显示 Dashboard 首页 ---
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue')
},
// --- 保持原有的入库页路由不变 ---
{
path: '/stock/inbound',
name: 'StockInbound',
component: () => import('@/views/stock/inbound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

View File

View File

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

View File

@ -0,0 +1,47 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 1. 创建 axios 实例
const service = axios.create({
// 这里的 '/api' 配合 vite.config.ts 的 proxy 使用
baseURL: '/api/v1',
timeout: 5000 // 请求超时时间
})
// 2. 请求拦截器 (可以在这里加 Token)
service.interceptors.request.use(
(config) => {
// 如果以后有登录 token就在这里加
// const token = localStorage.getItem('token')
// if (token) {
// config.headers['Authorization'] = 'Bearer ' + token
// }
return config
},
(error) => {
return Promise.reject(error)
}
)
// 3. 响应拦截器 (统一处理错误)
service.interceptors.response.use(
(response) => {
const res = response.data
// 这里可以根据后端的 code 来判断
// 假设你的后端成功返回 code: 200
if (res.code && res.code !== 200) {
ElMessage.error(res.msg || 'Error')
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res // 直接返回数据部分
}
},
(error) => {
console.log('err' + error)
ElMessage.error(error.message || '请求失败')
return Promise.reject(error)
}
)
// 4. 【关键】必须默认导出 service
export default service

View File

View File

@ -0,0 +1,61 @@
<template>
<div class="dashboard-container">
<el-card class="welcome-card">
<template #header>
<div class="card-header">
<span>👋 欢迎回来</span>
</div>
</template>
<div class="card-body">
<h2>欢迎使用 IRIS 库存管理系统</h2>
<p>请点击下方按钮进入具体业务模块</p>
<div class="action-buttons">
<el-button type="primary" size="large" @click="$router.push('/stock/inbound')">
<el-icon style="margin-right: 5px"><Box /></el-icon>
进入采购入库
</el-button>
<el-button size="large" disabled>
<el-icon style="margin-right: 5px"><TrendCharts /></el-icon>
库存报表 (开发中)
</el-button>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { Box, TrendCharts } from '@element-plus/icons-vue'
</script>
<style scoped>
.dashboard-container {
padding: 20px;
display: flex;
justify-content: center;
margin-top: 50px;
}
.welcome-card {
width: 600px;
text-align: center;
}
.card-body h2 {
color: #303133;
margin-bottom: 10px;
}
.card-body p {
color: #606266;
margin-bottom: 30px;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
}
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,237 @@
<template>
<div class="app-container" style="padding: 20px;">
<div style="margin-bottom: 20px; display: flex; justify-content: space-between;">
<el-button type="primary" :icon="Plus" @click="handleCreate">新增入库</el-button>
<el-button :icon="Refresh" @click="fetchData">刷新</el-button>
</div>
<el-table v-loading="loading" :data="tableData" border stripe style="width: 100%">
<el-table-column prop="inbound_date" label="入库时间" width="160" />
<el-table-column prop="sku_code" label="SKU" width="120" fixed="left" />
<el-table-column prop="material_name" label="物料名称" min-width="150" />
<el-table-column prop="spec_model" label="规格" width="120" />
<el-table-column prop="category" label="分类" width="100" />
<el-table-column prop="unit" label="单位" width="60" />
<el-table-column prop="qty_inbound" label="入库量" width="100">
<template #default="{ row }">
<span style="font-weight: bold; color: #409EFF">{{ row.qty_inbound }}</span>
</template>
</el-table-column>
<el-table-column prop="price_unit" label="单价" width="100" />
<el-table-column prop="price_total" label="总价" width="100" />
<el-table-column prop="warehouse_loc" label="库位" width="100" />
<el-table-column prop="supplier_name" label="供应商" width="120" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px; text-align: right;">
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.pageSize"
:total="total"
layout="total, prev, pager, next"
@current-change="fetchData"
/>
</div>
<el-dialog
:title="dialogType === 'create' ? '入库录入 (支持自动建档)' : '编辑入库单'"
v-model="dialogVisible"
width="650px"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<div v-if="dialogType === 'create'">
<el-divider content-position="left">物料基础信息</el-divider>
<el-alert title="输入SKU后如是新物料请补全名称和规格如是旧物料系统会自动关联。" type="info" :closable="false" style="margin-bottom:15px;" />
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="SKU编码" prop="sku_code">
<el-input v-model="form.sku_code" placeholder="唯一识别码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物料名称" prop="material_name">
<el-input v-model="form.material_name" placeholder="新物料必填" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="规格型号" prop="spec_model">
<el-input v-model="form.spec_model" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分类" prop="category">
<el-input v-model="form.category" placeholder="如: 电子料" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="单位" prop="unit">
<el-input v-model="form.unit" placeholder="个/包" />
</el-form-item>
</el-col>
</el-row>
</div>
<el-divider content-position="left">入库业务信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="入库数量" prop="qty_inbound">
<el-input-number v-model="form.qty_inbound" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单价" prop="price_unit">
<el-input-number v-model="form.price_unit" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="库位" prop="warehouse_loc">
<el-input v-model="form.warehouse_loc" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="批次号" prop="batch_no">
<el-input v-model="form.batch_no" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="供应商" prop="supplier_name">
<el-input v-model="form.supplier_name" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">确认提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Plus, Refresh } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getInboundList, createInbound, updateInbound, deleteInbound } from '@/api/stock'
const loading = ref(false)
const submitting = ref(false)
const tableData = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const dialogType = ref<'create' | 'update'>('create')
const formRef = ref()
const queryParams = reactive({ page: 1, pageSize: 10 })
// 表单对象
const form = reactive({
id: undefined,
sku_code: '',
material_name: '',
spec_model: '',
category: '',
unit: '',
qty_inbound: 0,
price_unit: 0,
warehouse_loc: '',
batch_no: '',
supplier_name: ''
})
const rules = {
sku_code: [{ required: true, message: 'SKU不能为空', trigger: 'blur' }],
qty_inbound: [{ required: true, message: '数量必须大于0', trigger: 'blur' }]
}
const fetchData = async () => {
loading.value = true
try {
const res = await getInboundList(queryParams)
tableData.value = res.data.items
total.value = res.data.total
} finally {
loading.value = false
}
}
const handleCreate = () => {
dialogType.value = 'create'
// 重置表单
Object.assign(form, {
id: undefined, sku_code: '', material_name: '', spec_model: '', category: '', unit: '',
qty_inbound: 1, price_unit: 0, warehouse_loc: '', batch_no: '', supplier_name: ''
})
dialogVisible.value = true
}
const handleUpdate = (row: any) => {
dialogType.value = 'update'
// 仅允许修改入库相关信息
Object.assign(form, row)
dialogVisible.value = true
}
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitting.value = true
try {
if (dialogType.value === 'create') {
await createInbound(form)
ElMessage.success('入库成功')
} else {
// 编辑时只提交入库单ID和可修改字段
await updateInbound(form.id!, {
qty_inbound: form.qty_inbound,
price_unit: form.price_unit,
warehouse_loc: form.warehouse_loc,
batch_no: form.batch_no,
supplier_name: form.supplier_name
})
ElMessage.success('更新成功')
}
dialogVisible.value = false
fetchData()
} catch (e: any) {
ElMessage.error(e.response?.data?.msg || '操作失败')
} finally {
submitting.value = false
}
}
})
}
const handleDelete = (row: any) => {
ElMessageBox.confirm('确认删除该记录?', '警告', { type: 'warning' })
.then(async () => {
await deleteInbound(row.id)
ElMessage.success('已删除')
fetchData()
})
}
onMounted(() => fetchData())
</script>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>