<script setup lang="ts">
|
import { computed, reactive, ref } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import {
|
createMaterialCategory,
|
generateMaterialCategoryCode,
|
getMaterialCategory,
|
getMaterialCategoryList,
|
updateMaterialCategory,
|
type MaterialCategory,
|
} from "../../../api/basic-data";
|
import { useAuth } from "../../../stores/auth";
|
const { state } = useAuth();
|
const id = ref<number>();
|
const saving = ref(false);
|
const generatingCode = ref(false);
|
const categories = ref<MaterialCategory[]>([]);
|
const form = reactive<MaterialCategory>({
|
code: "",
|
name: "",
|
parentId: 0,
|
itemOrProduct: "ITEM",
|
sort: 0,
|
status: 0,
|
});
|
const parents = computed(() => [
|
{ id: 0, name: "顶级分类" },
|
...flatten(categories.value),
|
]);
|
function flatten(
|
list: MaterialCategory[],
|
depth = 0
|
): { id: number; name: string }[] {
|
return list.flatMap(x =>
|
x.id
|
? [
|
{ id: x.id, name: `${" ".repeat(depth)}${x.name || "未命名分类"}` },
|
...flatten(x.children || [], depth + 1),
|
]
|
: []
|
);
|
}
|
async function generateCode() {
|
if (!state.accessToken || generatingCode.value) return;
|
generatingCode.value = true;
|
try {
|
form.code = await generateMaterialCategoryCode(state.accessToken);
|
} catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : "编码生成失败",
|
icon: "none",
|
});
|
} finally {
|
generatingCode.value = false;
|
}
|
}
|
async function save() {
|
if (!form.code?.trim() || !form.name?.trim())
|
return uni.showToast({ title: "请填写分类编码和名称", icon: "none" });
|
if (!state.accessToken || saving.value) return;
|
saving.value = true;
|
try {
|
const data = {
|
...form,
|
code: form.code.trim(),
|
name: form.name.trim(),
|
sort: Number(form.sort || 0),
|
};
|
if (id.value)
|
await updateMaterialCategory(state.accessToken, {
|
...data,
|
id: id.value,
|
});
|
else await createMaterialCategory(state.accessToken, data);
|
uni.showToast({ title: "保存成功", icon: "success" });
|
setTimeout(() => uni.navigateBack(), 500);
|
} catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : "保存失败",
|
icon: "none",
|
});
|
} finally {
|
saving.value = false;
|
}
|
}
|
onLoad(async query => {
|
id.value = query.id ? Number(query.id) : undefined;
|
if (!id.value && query.parentId) form.parentId = Number(query.parentId);
|
try {
|
if (!state.accessToken) return;
|
categories.value = await getMaterialCategoryList(state.accessToken);
|
if (id.value)
|
Object.assign(
|
form,
|
await getMaterialCategory(state.accessToken, id.value)
|
);
|
} catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : "数据加载失败",
|
icon: "none",
|
});
|
}
|
});
|
</script>
|
<template>
|
<view class="page">
|
<view class="form-card">
|
<view class="field"><text>上级分类</text>
|
<picker :range="parents"
|
range-key="name"
|
@change="form.parentId = parents[$event.detail.value].id">
|
<view class="picker"><text>{{ parents.find(x => x.id === form.parentId)?.name || '顶级分类' }}</text><text class="picker-arrow">›</text></view>
|
</picker>
|
</view>
|
<view class="field code-field"><text>分类编码 <text class="required">*</text></text><input v-model="form.code"
|
placeholder="请输入分类编码" /><button class="generate-code"
|
:loading="generatingCode"
|
:disabled="generatingCode"
|
@click="generateCode">{{ generatingCode ? '生成中' : '自动生成' }}</button></view>
|
<view class="field"><text>分类名称 <text class="required">*</text></text><input v-model="form.name"
|
placeholder="请输入分类名称" /></view>
|
<view class="field"><text>类型</text><radio-group @change="form.itemOrProduct = $event.detail.value"><label>
|
<radio :checked="form.itemOrProduct === 'ITEM'"
|
value="ITEM" />物料
|
</label><label>
|
<radio :checked="form.itemOrProduct === 'PRODUCT'"
|
value="PRODUCT" />产品
|
</label></radio-group></view>
|
<view class="field"><text>排序</text><input v-model="form.sort"
|
type="number"
|
placeholder="0" /></view>
|
<view class="field"><text>状态</text><radio-group @change="form.status = Number($event.detail.value)"><label>
|
<radio :checked="form.status === 0"
|
value="0" />启用
|
</label><label>
|
<radio :checked="form.status === 1"
|
value="1" />停用
|
</label></radio-group></view>
|
<view class="field column"><text>备注</text><textarea v-model="form.remark"
|
placeholder="请输入备注" ></textarea></view>
|
</view><button class="save"
|
:loading="saving"
|
:disabled="saving"
|
@click="save">保存</button>
|
</view>
|
</template>
|
<style lang="scss" scoped>
|
.page {
|
min-height: 100vh;
|
padding: 24rpx 30rpx 70rpx;
|
background: #f6f7f9;
|
}
|
.form-card {
|
background: #fff;
|
border-radius: 16rpx;
|
overflow: hidden;
|
}
|
.field {
|
display: flex;
|
align-items: center;
|
min-height: 96rpx;
|
padding: 0 24rpx;
|
border-bottom: 1rpx solid #eef1f5;
|
gap: 20rpx;
|
}
|
.field > text {
|
width: 180rpx;
|
color: #33455d;
|
font-size: 27rpx;
|
}
|
.field input,
|
.field picker,
|
.picker {
|
flex: 1;
|
color: #1e3049;
|
font-size: 27rpx;
|
}
|
.code-field input {
|
min-width: 0;
|
}
|
.generate-code {
|
flex: none;
|
min-width: 136rpx;
|
margin: 0;
|
padding: 0 16rpx;
|
color: var(--color-primary, #1769d8);
|
background: #fff;
|
border: 1rpx solid var(--color-primary, #1769d8);
|
border-radius: 8rpx;
|
font-size: 24rpx;
|
line-height: 56rpx;
|
}
|
.generate-code::after {
|
border: 0;
|
}
|
.picker {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
color: #65758a;
|
}
|
.picker > text:first-child {
|
min-width: 0;
|
flex: 1;
|
}
|
.picker-arrow {
|
margin-left: 16rpx;
|
color: #8092a7;
|
font-size: 36rpx;
|
line-height: 1;
|
}
|
.field radio-group {
|
flex: 1;
|
display: flex;
|
gap: 30rpx;
|
font-size: 26rpx;
|
}
|
.column {
|
align-items: flex-start;
|
flex-direction: column;
|
padding: 20rpx 24rpx;
|
}
|
.column > text {
|
width: auto;
|
}
|
.column textarea {
|
width: 100%;
|
height: 130rpx;
|
font-size: 27rpx;
|
}
|
.save {
|
margin-top: 32rpx;
|
color: #fff;
|
background: var(--color-primary, #1769d8);
|
border-radius: 12rpx;
|
font-size: 30rpx;
|
}
|
.save::after {
|
border: 0;
|
}
|
</style>
|