You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

698 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<!-- 顶部图片和标题 -->
<view class="header-section">
<image
class="header-image"
src="/static/imgs/indexImg.png"
mode="aspectFill"
></image>
<view class="header-overlay">
<view class="header-title">生成电梯图纸</view>
<view class="header-subtitle">
选择模板并设置参数快速生成专业电梯施工图
</view>
</view>
</view>
<!-- 选择图纸模板 -->
<view class="section">
<view class="section-title">选择图纸模板</view>
<uv-skeletons
v-if="loading"
:loading="loading"
:skeleton="[
{
type: 'flex',
num: 2,
gap: '20rpx',
children: [
{
type: 'custom',
num: 1,
gap: '20rpx',
style: 'width: 300rpx;height:200rpx;marginLeft: 10rpx;',
},
{
type: 'custom',
num: 1,
gap: '20rpx',
style: 'width: 300rpx;height:200rpx;marginLeft: 10rpx;',
},
],
},
]"
></uv-skeletons>
<view class="template-grid" v-else>
<view
class="template-item"
:class="{ active: currentTemplate.id === item.id }"
@click="selectTemplate(item)"
v-for="item in templateList"
:key="item.id"
>
<view class="tag" v-if="item.price_cents">
¥ {{ item.price_cents / 100 }}
</view>
<view class="template-name">{{ item.name }}</view>
<view class="template-desc">{{ item.description }}</view>
</view>
</view>
</view>
<!-- 设置参数 -->
<view class="section" v-if="has_access">
<view class="section-title">设置参数</view>
<uv-skeletons
v-if="params_loading"
:loading="params_loading"
:skeleton="[
{
type: 'line',
num: 3,
gap: '20rpx',
},
]"
></uv-skeletons>
<view class="form" v-else>
<uv-form
labelAlign="left"
labelWidth="auto"
labelPosition="left"
:model="formField"
:rules="rules"
ref="form"
>
<uv-form-item
:prop="item.name"
:label="item.name + ''"
v-for="item in currentTemplate.params?.filter(
(item) => item.enabled
) || []"
:key="item.name"
>
<div class="required" v-if="item.required" style="color: #f12323">
*
</div>
<uv-input
@click="openPicker(item)"
@input="handleInputChange(item)"
:readonly="item.type === 'select' || item.name === '土建图号'"
:type="item.type === 'number' ? 'digit' : 'text'"
v-model="item.value"
color="#000"
border="bottom"
:placeholderStyle="{ color: '#666778' }"
:placeholder="
item.name === '土建图号' ? '将根据土建图型号自动生成' : '请输入'
"
></uv-input>
</uv-form-item>
<!-- <uv-form-item prop="password" label="载重kg">
<uv-input
v-model="formField.password"
color="#000"
border="none"
:placeholderStyle="{ color: '#666778' }"
placeholder="请输入"
></uv-input>
</uv-form-item>
<uv-form-item prop="password" label="井道宽度mm">
<uv-input
type="digit"
v-model="formField.password"
color="#000"
border="none"
:placeholderStyle="{ color: '#666778' }"
placeholder="请输入"
></uv-input>
</uv-form-item>
<uv-form-item prop="password" label="井道深度mm">
<uv-input
type="digit"
v-model="formField.password"
color="#000"
border="none"
:placeholderStyle="{ color: '#666778' }"
placeholder="请输入"
></uv-input>
</uv-form-item>
<uv-form-item prop="password" label="顶层高度mm">
<uv-input
type="digit"
v-model="formField.password"
color="#000"
border="none"
:placeholderStyle="{ color: '#666778' }"
placeholder="请输入"
></uv-input>
</uv-form-item>
<uv-form-item prop="password" label="底坑深度mm">
<uv-input
type="digit"
v-model="formField.password"
color="#000"
border="none"
:placeholderStyle="{ color: '#666778' }"
placeholder="请输入"
></uv-input>
</uv-form-item>
<uv-form-item prop="password">
<div style="margin-bottom: 4px">备注说明</div>
<uv-textarea
v-model="formField.password"
placeholder="请输入"
></uv-textarea>
</uv-form-item> -->
</uv-form>
</view>
</view>
<view class="section" v-else>
<div class="status">
申请状态:{{ get_access_status(currentTemplate.access_status) }}
</div>
<uv-button
size="small"
v-if="currentTemplate.access_status !== 'pending'"
@click="openApplyModal"
text="申请权限"
color="#3F70FF"
></uv-button>
</view>
<!-- 底部按钮 -->
<view class="bottom-actions">
<uv-button
:loading="generating"
@click="modalRefOpen"
text="生成图纸"
color="#3F70FF"
></uv-button>
<uv-button @click="viewHistory" text="查看记录"></uv-button>
</view>
<uv-modal
ref="modalRef"
showCancelButton
content="确定生成图纸?"
@confirm="confirmGenerate"
></uv-modal>
<uv-modal
ref="applyModalRef"
showCancelButton
title="申请权限"
@confirm="confirmApply"
@cancel="cancelApply"
>
<view class="apply-modal-content">
<view class="apply-label">申请理由:</view>
<uv-textarea
v-model="applyReason"
placeholder="请输入申请理由"
maxlength="200"
count
></uv-textarea>
</view>
</uv-modal>
<uv-picker ref="picker" :columns="columns" @confirm="confirm"></uv-picker>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import {
onShow,
onLoad,
onShareAppMessage,
onShareTimeline,
onReachBottom,
onPullDownRefresh,
} from "@dcloudio/uni-app";
import { useAuthStore, useUserStore } from "@/store";
import RPC from "@/utils/request";
import { usePermission } from "@/hooks/usePermission";
const authStore = useAuthStore();
const userStore = useUserStore();
const loading = ref(false);
const params_loading = ref(false);
const has_access = ref(false);
const applyModalRef = ref(null);
const applyReason = ref("");
const applyLoading = ref(false);
const get_access_status = (access_status) => {
let str = "";
if (access_status === "none") {
str = "无权限";
} else if (access_status === "pending") {
str = "等待审批...";
} else if (access_status === "rejected") {
str = "已拒绝";
} else if (access_status === "approved") {
str = "已同意";
} else {
str = access_status;
}
return str;
};
const modalRefOpen = () => {
if (!has_access.value) {
uni.showToast({
title: "模板无权限使用",
icon: "error",
});
return;
}
let flag = false;
let name;
currentTemplate.value.params
?.filter((item) => item.enabled)
.forEach((item) => {
if (item.required && !item.value) {
flag = true;
name = item.name;
}
});
if (flag) {
uni.showToast({
title: `请检查【${name}】`,
icon: "none",
});
return;
}
modalRef.value.open();
};
const currentParams = ref(null);
const openPicker = (item) => {
console.log(item);
if (item.type !== "select") return;
currentParams.value = item;
columns.value = [item.options.split(",")];
picker.value.open();
};
const picker = ref(null);
const columns = ref([[]]);
const confirm = (val) => {
console.log(val);
currentParams.value.value = val.value[0];
// 如果修改的是土建图型号,需要重新生成土建图号
if (currentParams.value.name === "土建图型号") {
nextTick(() => {
generateDrawingNumber();
})
}
};
// 打开申请权限弹框
const openApplyModal = () => {
applyReason.value = "";
applyModalRef.value.open();
};
// 确认申请权限
const confirmApply = async () => {
if (!applyReason.value.trim()) {
uni.showToast({
title: "请输入申请理由",
icon: "error",
});
return;
}
applyLoading.value = true;
try {
await RPC.post("/template_access_apply", {
template_id: currentTemplate.value.id,
reason: applyReason.value.trim(),
});
uni.showToast({
title: "申请已提交",
icon: "success",
});
applyModalRef.value.close();
applyReason.value = "";
getList();
} catch (error) {
uni.showToast({
title: "申请失败,请重试",
icon: "error",
});
} finally {
applyLoading.value = false;
}
};
// 取消申请
const cancelApply = () => {
applyReason.value = "";
};
// 处理输入变化
const handleInputChange = (item) => {
// 如果修改的是土建图型号,需要重新生成土建图号
if (item.name === "土建图型号") {
nextTick(() => {
generateDrawingNumber();
})
}
};
// 生成土建图号
const generateDrawingNumber = () => {
const params = currentTemplate.value.params;
if (!params) return;
// 找到土建图型号和土建图号字段
const modelParam = params.find((p) => p.name === "土建图型号");
const numberParam = params.find((p) => p.name === "土建图号");
if (!modelParam || !numberParam || !modelParam.value) return;
// 获取当前年份和月份
const now = new Date();
const year = now.getFullYear().toString().slice(-2); // 取年份后两位
const month = (now.getMonth() + 1).toString().padStart(2, "0"); // 月份补零
// 生成4位随机数字+字母
const randomPart = generateRandomCode();
// 组合生成土建图号GSE400-V25-10-0002A
numberParam.value = `${modelParam.value}-V${year}${month}-${randomPart}`;
};
// 生成随机编码4位数字+1位字母
const generateRandomCode = () => {
const numbers = Math.floor(Math.random() * 10000)
.toString()
.padStart(4, "0");
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const letter = letters.charAt(Math.floor(Math.random() * letters.length));
return numbers + letter;
};
const generating = ref(false);
const modalRef = ref(null);
const confirmGenerate = () => {
const params = {};
let flag = false;
let name = "";
currentTemplate.value.params
?.filter((item) => item.enabled)
.forEach((item) => {
if (item.required && !item.value) {
flag = true;
name = item.name;
}
params[item.name] = item.value;
});
if (flag) {
uni.showToast({
title: `请检查【${name}】`,
icon: "none",
});
return;
}
RPC.post("/template_create_task", {
template_id: currentTemplate.value.id,
template_data: params,
}).then(() => {
uni.showToast({
title: "图纸开始生成",
icon: "success",
});
setTimeout(() => {
uni.navigateTo({
url: "/pagesA/generate-record/generate-record",
});
}, 1000);
modalRef.value.close();
});
};
const templateList = ref([]);
const currentTemplate = ref({});
const getList = () => {
loading.value = true;
RPC.post("/template/list_with_access", {
page: 1,
limit: -1,
})
.then((res) => {
templateList.value = (res.rows || []).filter((item) => item.enabled);
currentTemplate.value = templateList.value[0]
? JSON.parse(JSON.stringify(templateList.value[0]))
: {};
if (currentTemplate.value.has_access) {
params_loading.value = true;
has_access.value = true;
RPC.post("/template/detail", { id: currentTemplate.value.id })
.then((res) => {
currentTemplate.value.params = res?.params;
})
.finally(() => {
params_loading.value = false;
});
}
})
.finally(() => {
loading.value = false;
});
};
// 选择模板
const selectTemplate = (item) => {
currentTemplate.value = JSON.parse(JSON.stringify(item));
has_access.value = currentTemplate.value.has_access;
if (currentTemplate.value.has_access) {
params_loading.value = true;
RPC.post("/template/detail", { id: currentTemplate.value.id })
.then((res) => {
currentTemplate.value.params = res?.params;
// 如果有土建图型号,自动生成土建图号
nextTick(() => {
generateDrawingNumber();
})
})
.finally(() => {
params_loading.value = false;
});
}
};
const form = ref(null);
const formField = ref({
phone: "",
password: "",
});
const rules = ref({
// 'phone': {
// required: true,
// message: '请填写手机号',
// trigger: ['blur', 'change']
// },
// 'password': {
// required: true,
// message: '请填写验证码',
// trigger: ['blur', 'change']
// }
});
// 查看历史记录
const viewHistory = () => {
uni.navigateTo({
url: "/pagesA/generate-record/generate-record",
});
};
// 分享给朋友
onShareAppMessage((data) => {
console.log("🚀 ~ onShareAppMessage ~ data:", data);
return {
title: "电梯图纸生成平台",
path: "/pages/index/index",
};
});
// 分享到朋友圈
onShareTimeline((data) => {
console.log("🚀 ~ onShareTimeline ~ data:", data);
return {
title: "电梯图纸生成平台",
query: "a=1&b=2",
};
});
onLoad(() => {
console.log("index-onLoad");
});
onShow(async () => {
getList();
console.log("test page onShow");
const hasPermission = await usePermission();
console.log(hasPermission ? "已登录" : "跳转登录");
// 以下开始写业务逻辑...
if (!hasPermission) {
authStore.signOut();
uni.$uv.route("/pages/login/login");
}
});
onPullDownRefresh(() => {
getList();
uni.stopPullDownRefresh();
});
</script>
<style lang="scss" scoped>
.container {
font-size: 14px;
min-height: 100vh;
background-color: #f8f8f8;
padding-bottom: 52px;
/* #ifdef H5 */
padding-bottom: 102px;
/* H5环境下需要更多空间 (50px按钮 + 50px tabBar + 2px间距) */
/* #endif */
}
.header-section {
position: relative;
height: 200px;
overflow: hidden;
.header-image {
width: 100%;
height: 100%;
}
.header-overlay {
position: absolute;
top: 50%;
right: 0;
background: rgba(0, 0, 0, 0.5);
color: white;
width: 70%;
transform: translate(0, -50%);
padding: 20px 10px;
.header-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
color: #bd9e85;
}
.header-subtitle {
font-size: 12px;
}
}
}
.section {
margin: 16px;
background: white;
border-radius: 12px;
padding: 16px;
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
}
.status {
margin-bottom: 20px;
}
}
.template-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-height: 200px;
overflow-y: auto;
.template-item {
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 16px 12px;
text-align: center;
transition: all 0.3s;
position: relative;
.tag {
position: absolute;
top: 0px;
left: 0;
padding: 2px 8px;
border-radius: 0 0 10px 0;
font-size: 10px;
color: white;
background: #00caef;
}
&.active {
border-color: #4285f4;
background-color: #f0f7ff;
}
.template-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin: 8px 0 4px;
}
.template-desc {
font-size: 12px;
color: #999;
}
}
}
.bottom-actions {
z-index: 999;
position: fixed;
bottom: 0;
left: 0;
background: #fff;
height: 50px;
border-top: 1px solid #e5e5e5;
display: flex;
align-items: center;
gap: 12px;
justify-content: space-between;
padding: 0 16px;
box-sizing: border-box;
width: 100%;
/* #ifdef H5 */
bottom: 50px;
/* H5环境下给tabBar留出空间 */
/* #endif */
}
.apply-modal-content {
padding: 20px 0;
width: 100%;
.apply-label {
font-size: 14px;
color: #333;
margin-bottom: 12px;
}
}
</style>
<style lang="scss">
.bottom-actions {
.uv-button-wrapper {
width: 48%;
}
}
</style>