20260810172235

This commit is contained in:
oneao committed 2026-08-10 17:22:36 +08:00
1 parent c4891538a7
commit ed5ab3885d
35 files changed
+1314 -653

No files matched your search

@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">联系人</h1>
</div>
</template>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">合作伙伴</h1>
</div>
</template>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">其他数据</h1>
</div>
</template>
+73 -1
View File
@@ -1,8 +1,80 @@
<script setup lang="ts">
import { ref } from "vue"
import FmsSelect from "@/components/FmsSelect.vue"
import FmsAutoComplete from "@/components/FmsAutoComplete.vue"
// FmsSelect demo
const localeCode = ref("")
const localeLabel = ref("")
const localeRecord = ref<Record<string, any> | null>(null)
function onLocaleChange(record: Record<string, any> | null) {
localeRecord.value = record
}
// FmsAutoComplete demo
const autoValue = ref("")
const autoRecord = ref<Record<string, any> | null>(null)
function onAutoChange(record: Record<string, any> | null) {
autoRecord.value = record
}
</script>
<template>
<div class="flex h-full items-center justify-center">
<div class="flex h-full flex-col items-center gap-8 p-8">
<h1 class="text-2xl font-bold text-muted-foreground">Dashboard</h1>
<div class="grid w-full max-w-2xl gap-8">
<!-- FmsSelect demo -->
<div class="rounded-lg border p-6 space-y-3">
<h2 class="text-sm font-semibold">FmsSelect(多语言类型)</h2>
<p class="text-xs text-muted-foreground">
v-model + v-model:label 双绑定,支持远程搜索、滚动分页、清除、未匹配值回显
</p>
<div class="w-64">
<FmsSelect
v-model="localeCode"
v-model:label="localeLabel"
view-name="b_i18n_type"
label-key="b_name"
value-key="b_code"
order-by="b_code ASC"
:page-size="10"
allow-clear
placeholder="选择语言类型..."
@change="onLocaleChange"
/>
</div>
<div class="text-xs text-muted-foreground space-y-0.5">
<div>value(b_code):<code class="text-foreground">{{ localeCode || '-' }}</code></div>
<div>label(b_name):<code class="text-foreground">{{ localeLabel || '-' }}</code></div>
<div>record:<code class="text-foreground">{{ localeRecord || '-' }}</code></div>
</div>
</div>
<!-- FmsAutoComplete demo -->
<div class="rounded-lg border p-6 space-y-3">
<h2 class="text-sm font-semibold">FmsAutoComplete(语言类型搜索)</h2>
<p class="text-xs text-muted-foreground">
单 v-model 绑定文本,回车/选中后拿到完整记录
</p>
<div class="w-64">
<FmsAutoComplete
v-model="autoValue"
view-name="b_i18n_type"
label-key="b_name"
value-key="b_code"
order-by="b_code ASC"
placeholder="输入语言关键字..."
@change="onAutoChange"
/>
</div>
<div class="text-xs text-muted-foreground space-y-0.5">
<div>v-model:<code class="text-foreground">{{ autoValue || '-' }}</code></div>
<div>record:<code class="text-foreground">{{ autoRecord || '-' }}</code></div>
</div>
</div>
</div>
</div>
</template>
@@ -1,63 +0,0 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
const stats = [
{ label: '总用户数', value: '12,345', trend: '+12.5%', up: true },
{ label: '活跃用户', value: '8,920', trend: '+8.2%', up: true },
{ label: '本月新增', value: '1,203', trend: '-3.1%', up: false },
{ label: '转化率', value: '24.8%', trend: '+5.6%', up: true },
]
</script>
<template>
<div class="space-y-6">
<div>
<h2 class="text-xl font-semibold">卡片示例</h2>
<p class="mt-1 text-sm text-muted-foreground">统计卡片和内容卡片</p>
</div>
<!-- Stat cards -->
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Card v-for="stat in stats" :key="stat.label">
<CardHeader class="pb-2">
<CardDescription>{{ stat.label }}</CardDescription>
</CardHeader>
<CardContent class="pb-2">
<div class="text-2xl font-bold">{{ stat.value }}</div>
</CardContent>
<CardFooter class="text-xs" :class="stat.up ? 'text-emerald-600' : 'text-red-500'">
{{ stat.trend }}
</CardFooter>
</Card>
</div>
<!-- Content cards -->
<div class="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>最近活动</CardTitle>
<CardDescription>团队最近的操作记录</CardDescription>
</CardHeader>
<CardContent class="space-y-2 text-sm text-muted-foreground">
<p>• 张三 新增了一条客户记录</p>
<p>• 李四 更新了产品配置</p>
<p>• 王五 导出了本月报表</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>快捷操作</CardTitle>
<CardDescription>常用功能入口</CardDescription>
</CardHeader>
<CardContent class="flex flex-wrap gap-2">
<Button variant="outline">新建订单</Button>
<Button variant="outline">导入数据</Button>
<Button variant="outline">生成报表</Button>
<Button variant="outline">发送通知</Button>
</CardContent>
</Card>
</div>
</div>
</template>
@@ -1,48 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
const form = ref({ name: '', email: '', phone: '' })
const submitted = ref(false)
function handleSubmit() {
submitted.value = true
setTimeout(() => { submitted.value = false }, 3000)
}
</script>
<template>
<div class="space-y-6">
<div>
<h2 class="text-xl font-semibold">表单示例</h2>
<p class="mt-1 text-sm text-muted-foreground">基础的输入表单</p>
</div>
<div class="max-w-lg rounded-lg border bg-card p-6">
<form class="space-y-4" @submit.prevent="handleSubmit">
<div class="space-y-2">
<Label for="name">姓名</Label>
<Input id="name" v-model="form.name" placeholder="请输入姓名" />
</div>
<div class="space-y-2">
<Label for="email">邮箱</Label>
<Input id="email" v-model="form.email" type="email" placeholder="请输入邮箱地址" />
</div>
<div class="space-y-2">
<Label for="phone">手机号</Label>
<Input id="phone" v-model="form.phone" placeholder="请输入手机号" />
</div>
<div class="flex items-center gap-3 pt-2">
<Button type="submit">提交</Button>
<Button type="button" variant="outline">重置</Button>
</div>
</form>
<div v-if="submitted" class="mt-4 rounded-md bg-emerald-50 px-4 py-3 text-sm text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300">
表单提交成功!提交的数据:{{ form }}
</div>
</div>
</div>
</template>
@@ -1,71 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
const settings = ref({
notifications: true,
autoSave: true,
compactMode: false,
analytics: true,
})
</script>
<template>
<div class="space-y-6">
<div>
<h2 class="text-xl font-semibold">设置示例</h2>
<p class="mt-1 text-sm text-muted-foreground">系统设置与偏好</p>
</div>
<Card class="max-w-2xl">
<CardHeader>
<CardTitle>通知设置</CardTitle>
<CardDescription>管理系统通知偏好</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div class="flex items-center justify-between">
<div>
<Label>消息通知</Label>
<p class="text-sm text-muted-foreground">接收系统消息和提醒</p>
</div>
<Switch v-model="settings.notifications" />
</div>
<div class="flex items-center justify-between">
<div>
<Label>自动保存</Label>
<p class="text-sm text-muted-foreground">编辑时自动保存内容</p>
</div>
<Switch v-model="settings.autoSave" />
</div>
<div class="flex items-center justify-between">
<div>
<Label>紧凑模式</Label>
<p class="text-sm text-muted-foreground">缩小间距,显示更多内容</p>
</div>
<Switch v-model="settings.compactMode" />
</div>
<div class="flex items-center justify-between">
<div>
<Label>数据统计</Label>
<p class="text-sm text-muted-foreground">允许收集使用数据</p>
</div>
<Switch v-model="settings.analytics" />
</div>
</CardContent>
</Card>
<Card class="max-w-2xl">
<CardHeader>
<CardTitle>危险操作</CardTitle>
<CardDescription>不可恢复的操作,请谨慎</CardDescription>
</CardHeader>
<CardContent class="flex gap-3">
<Button variant="outline" class="text-amber-600 border-amber-600 hover:bg-amber-50">导出所有数据</Button>
<Button variant="outline" class="text-red-600 border-red-600 hover:bg-red-50">删除账号</Button>
</CardContent>
</Card>
</div>
</template>
@@ -1,70 +0,0 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
const data = [
{ id: 1, name: '张三', department: '技术部', role: '前端开发', status: '在职' },
{ id: 2, name: '李四', department: '产品部', role: '产品经理', status: '在职' },
{ id: 3, name: '王五', department: '设计部', role: 'UI 设计', status: '休假' },
{ id: 4, name: '赵六', department: '技术部', role: '后端开发', status: '在职' },
{ id: 5, name: '钱七', department: '市场部', role: '市场运营', status: '离职' },
]
</script>
<template>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h2 class="text-xl font-semibold">表格示例</h2>
<p class="mt-1 text-sm text-muted-foreground">一个简单的数据表格页面</p>
</div>
<div class="flex items-center gap-2">
<Input placeholder="搜索..." class="w-48" />
<Button variant="outline">导出</Button>
<Button>新增</Button>
</div>
</div>
<div class="rounded-lg border bg-card">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="border-b bg-muted/50">
<tr>
<th class="px-4 py-3 text-left font-medium">姓名</th>
<th class="px-4 py-3 text-left font-medium">部门</th>
<th class="px-4 py-3 text-left font-medium">角色</th>
<th class="px-4 py-3 text-left font-medium">状态</th>
<th class="px-4 py-3 text-right font-medium">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id" class="border-b last:border-b-0 hover:bg-muted/50 transition-colors">
<td class="px-4 py-3">{{ row.name }}</td>
<td class="px-4 py-3">{{ row.department }}</td>
<td class="px-4 py-3 text-muted-foreground">{{ row.role }}</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-1.5 text-xs font-medium" :class="row.status === '在职' ? 'text-emerald-600' : row.status === '休假' ? 'text-amber-600' : 'text-muted-foreground'">
<span class="size-1.5 rounded-full" :class="row.status === '在职' ? 'bg-emerald-500' : row.status === '休假' ? 'bg-amber-500' : 'bg-muted-foreground'" />
{{ row.status }}
</span>
</td>
<td class="px-4 py-3 text-right">
<Button variant="ghost" size="xs">编辑</Button>
<Button variant="ghost" size="xs">查看</Button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="flex items-center justify-between text-sm text-muted-foreground">
<span>共 {{ data.length }} 条记录</span>
<div class="flex items-center gap-2">
<Button variant="outline" size="sm" disabled>上一页</Button>
<span>第 1 页</span>
<Button variant="outline" size="sm">下一页</Button>
</div>
</div>
</div>
</template>
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref } from "vue"
import { computed, onMounted, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useForm } from "vee-validate"
import { toast } from "vue-sonner"
import { loginApi } from "@/services/api"
import { useAuthStore } from "@/stores/auth"
import { Building2, GalleryVerticalEnd, KeyRound, User } from "@lucide/vue"
import { Building2, GalleryVerticalEnd, KeyRound, LoaderCircle, User } from "@lucide/vue"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
@@ -36,22 +38,32 @@ const formSchema = toTypedSchema(
}),
)
const form = useForm({
validationSchema: formSchema,
})
const submitting = ref(false)
const orgIdInput = ref<InstanceType<typeof Input> | null>(null)
onMounted(() => {
nextTick(() => {
(document.getElementById("orgId") || document.getElementById("password"))?.focus()
form.setValues({
orgId: authStore.orgId || '',
userId: authStore.user?.account || '',
password: '',
})
orgIdInput.value?.$el?.focus()
})
async function onSubmit(values: Record<string, unknown>) {
submitting.value = true
try {
const res = await loginApi(values.orgId as string, values.userId as string, (values.password as string) ?? "")
const { orgId, userId, password } = values as { orgId: string; userId: string; password?: string }
const res = await loginApi(orgId, userId, password ?? "")
authStore.setSession(
{ token: res.data.token, user: { ...res.data.user, account: res.data.user.id } },
res.data.orgid ?? (values.orgId as string),
{ token: res.data.token, user: { ...res.data.user, account: userId } },
res.data.orgid ?? orgId,
)
toast.success('登录成功')
router.replace(String(route.query.redirect || "/dashboard"))
} catch (e: unknown) {
if (e instanceof Error) alert(e.message)
@@ -59,12 +71,16 @@ async function onSubmit(values: Record<string, unknown>) {
submitting.value = false
}
}
function login() {
form.handleSubmit(onSubmit)()
}
</script>
<template>
<div class="flex min-h-svh flex-col items-center justify-center bg-background p-6 md:p-10">
<div class="w-full max-w-sm">
<Form class="flex flex-col gap-6" :validation-schema="formSchema" @submit="onSubmit">
<Form class="flex flex-col gap-6" :form="form">
<!-- Header -->
<div class="flex flex-col items-center gap-2 text-center">
<a href="#" class="flex flex-col items-center gap-2 font-medium">
@@ -89,7 +105,7 @@ async function onSubmit(values: Record<string, unknown>) {
<FormControl>
<div class="relative">
<span class="absolute inset-y-0 left-3 flex items-center text-muted-foreground"><Building2 class="size-4" /></span>
<Input id="orgId" v-bind="componentField" placeholder="请输入机构码" class="pl-9" :disabled="submitting" />
<Input id="orgId" ref="orgIdInput" v-bind="componentField" placeholder="请输入机构码" class="pl-9" :disabled="submitting" />
</div>
</FormControl>
<FormMessage />
@@ -124,8 +140,9 @@ async function onSubmit(values: Record<string, unknown>) {
</FormItem>
</FormField>
<Button type="submit" :disabled="submitting" class="w-full cursor-pointer">
{{ submitting ? "登录中..." : "登录" }}
<Button class="w-full cursor-pointer" :disabled="submitting" @click="login">
<LoaderCircle v-if="submitting" class="size-4 animate-spin" />
登录
</Button>
</Form>
</div>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">多语言类型</h1>
</div>
</template>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">登录日志</h1>
</div>
</template>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">模块管理</h1>
</div>
</template>
@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center">
<h1 class="text-2xl font-bold text-muted-foreground">用户管理</h1>
</div>
</template>