feat:增加主包(tabbar)、system(系统管理)、infra(基础设施)、bpm(工作流程)的页面

This commit is contained in:
YunaiV
2025-12-12 19:16:46 +08:00
parent c14e0d04d0
commit cc94b2a5f7
97 changed files with 12198 additions and 81 deletions

View File

@@ -0,0 +1,90 @@
<template>
<view class="input-item">
<wd-icon name="lock-on" size="20px" color="#1890ff" />
<wd-input
:model-value="modelValue"
placeholder="请输入验证码"
clearable
clear-trigger="focus"
no-border
type="number"
:maxlength="6"
@update:model-value="$emit('update:modelValue', $event)"
/>
<view
class="whitespace-nowrap border-l-1rpx border-l-[#e5e5e5] border-l-solid px-20rpx text-28rpx text-[#1890ff]"
@click="handleSendCode"
>
<text :class="{ 'text-gray-400': countdown > 0 }">
{{ countdown > 0 ? `${countdown} 秒后重发` : "获取验证码" }}
</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { onUnmounted, ref } from "vue";
import { useToast } from "wot-design-uni";
import { sendSmsCode } from "@/api/login";
import { isMobile } from "@/utils/validator";
defineOptions({
name: "CodeInput",
});
const props = defineProps<{
modelValue: string; // 验证码值 (v-model)
mobile: string; // 手机号
scene: number; // 短信场景21-登录 23-重置密码
beforeSend?: () => boolean; // 发送前的校验函数,返回 false 则不发送
}>();
defineEmits<{
"update:modelValue": [value: string];
}>();
const toast = useToast();
const countdown = ref(0); // 验证码倒计时,单位秒
let countdownTimer: ReturnType<typeof setInterval> | null = null; // 倒计时定时器
/** 页面卸载时清除倒计时定时器 */
onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
});
/** 发送验证码 */
async function handleSendCode() {
// 执行前置校验
if (props.beforeSend && !props.beforeSend()) {
return;
}
if (countdown.value > 0) {
return;
}
if (!props.mobile) {
toast.warning("请输入手机号");
return;
}
if (!isMobile(props.mobile)) {
toast.warning("请输入正确的手机号");
return;
}
// 发送验证码
await sendSmsCode({ mobile: props.mobile, scene: props.scene });
toast.success("验证码已发送");
// 开始倒计时
countdown.value = 60;
countdownTimer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(countdownTimer!);
countdownTimer = null;
}
}, 1000);
}
</script>

View File

@@ -0,0 +1,12 @@
<template>
<view class="header flex flex-col items-center pb-60rpx pt-120rpx">
<image class="mb-24rpx h-160rpx w-160rpx" src="/static/logo.svg" mode="aspectFit" />
<view class="text-44rpx text-[#1890ff] font-bold">
{{ title }}
</view>
</view>
</template>
<script lang="ts" setup>
const title = import.meta.env.VITE_APP_TITLE // 应用标题
</script>

View File

@@ -0,0 +1,134 @@
<template>
<view v-if="tenantEnabled" class="input-item">
<wd-icon name="home" size="20px" color="#1890ff" />
<wd-picker
:model-value="tenantId"
:columns="tenantList"
label-key="name"
value-key="id"
label=""
placeholder="请选择租户"
@confirm="handleConfirm"
/>
</view>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue";
import { useToast } from "wot-design-uni";
import {
getTenantByWebsite,
getTenantSimpleList,
type TenantVO,
} from "@/api/login";
import { useUserStore } from "@/store/user";
const toast = useToast();
const userStore = useUserStore();
const tenantEnabled = computed(
() => import.meta.env.VITE_APP_TENANT_ENABLE === "true",
); // 租户开关:通过环境变量控制
const tenantList = ref<TenantVO[]>([]); // 租户列表数据
const tenantId = computed(
() =>
userStore.tenantId ||
Number(import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT_ID) ||
undefined,
); // 当前选中的租户
/** 获取租户列表,并根据域名/appId 自动选中租户 */
async function fetchTenantList() {
if (!tenantEnabled.value) {
return;
}
try {
// 1. 并行获取租户列表和域名对应的租户
const websiteTenantPromise = fetchTenantByWebsite();
const list = await getTenantSimpleList();
tenantList.value = list || [];
// 2. 确定选中的租户:域名/appId > store 中的租户 > 列表第一个
let selectedTenantId: number | null = null;
// 2.1 优先使用域名/appId 对应的租户
const websiteTenant = await websiteTenantPromise;
if (websiteTenant?.id) {
selectedTenantId = websiteTenant.id;
}
// 2.2 如果没有从域名获取到,使用 store 中的租户
if (!selectedTenantId && userStore.tenantId) {
selectedTenantId = userStore.tenantId;
}
// 2.3 如果还是没有,使用列表第一个
if (!selectedTenantId && tenantList.value.length > 0) {
selectedTenantId = tenantList.value[0].id;
}
// 3. 设置选中的租户
if (selectedTenantId && selectedTenantId !== userStore.tenantId) {
userStore.setTenantId(selectedTenantId);
}
} catch (error) {
console.error("获取租户列表失败:", error);
}
}
/** 根据域名或 appId 获取租户 */
async function fetchTenantByWebsite(): Promise<TenantVO | null> {
try {
let website: string | null = null;
// #ifdef H5
// H5 环境:使用域名
if (window?.location?.hostname) {
website = window.location.hostname;
}
// #endif
// #ifdef MP
// 小程序环境:使用 appId
const appId = uni.getAccountInfoSync?.()?.miniProgram?.appId;
if (appId) {
website = appId;
}
// #endif
if (website) {
return await getTenantByWebsite(website);
}
} catch (error) {
// 域名未配置租户时会报错,忽略即可
console.debug("根据域名获取租户失败:", error);
}
return null;
}
/** 租户选择确认 */
function handleConfirm({ value }: { value: number }) {
userStore.setTenantId(value);
}
/** 校验租户是否已选择 */
function validate(): boolean {
if (!tenantEnabled.value) {
return true;
}
if (!tenantId.value) {
toast.warning("请选择租户");
return false;
}
return true;
}
/** 页面加载时获取租户列表 */
onMounted(() => {
fetchTenantList();
});
defineExpose({ validate });
</script>
<style lang="scss" scoped>
@import "../styles/auth.scss";
</style>