20260715221223
This commit is contained in:
1 parent
7049584d1e
commit
704dda07b5
21 files changed
+2649
-427
No files matched your search
@@ -0,0 +1,218 @@
|
||||
<script setup>
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
DollarOutlined,
|
||||
FileTextOutlined,
|
||||
PlusOutlined,
|
||||
} from "@antdv-next/icons";
|
||||
|
||||
const metrics = [
|
||||
{ label: "今日新增", value: "0", tone: "blue", icon: FileTextOutlined },
|
||||
{ label: "待处理", value: "0", tone: "amber", icon: ClockCircleOutlined },
|
||||
{ label: "本月应收", value: "¥ 0.00", tone: "green", icon: DollarOutlined },
|
||||
{ label: "本月完成", value: "0", tone: "gray", icon: CheckCircleOutlined },
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{ title: "单据编号", dataIndex: "id", key: "id" },
|
||||
{ title: "客户名称", dataIndex: "customer", key: "customer" },
|
||||
{ title: "业务状态", dataIndex: "status", key: "status" },
|
||||
{ title: "更新时间", dataIndex: "updatedAt", key: "updatedAt" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<section class="metric-grid" aria-label="业务概览">
|
||||
<article v-for="item in metrics" :key="item.label" class="metric-card">
|
||||
<div class="metric-card__copy">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
</div>
|
||||
<div class="metric-card__icon" :class="`metric-card__icon--${item.tone}`">
|
||||
<component :is="item.icon" />
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-grid">
|
||||
<div class="panel work-panel">
|
||||
<div class="panel__header">
|
||||
<div>
|
||||
<h2>待办业务</h2>
|
||||
<span>最近更新</span>
|
||||
</div>
|
||||
<a-button type="primary">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建业务
|
||||
</a-button>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="[]"
|
||||
:pagination="false"
|
||||
size="middle"
|
||||
:scroll="{ x: 620 }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="panel quick-panel">
|
||||
<div class="panel__header">
|
||||
<div>
|
||||
<h2>快捷入口</h2>
|
||||
<span>常用操作</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="quick-links">
|
||||
<router-link to="/business/orders">
|
||||
<FileTextOutlined />
|
||||
<span>业务单据</span>
|
||||
</router-link>
|
||||
<router-link to="/fees/receivable">
|
||||
<DollarOutlined />
|
||||
<span>应收费用</span>
|
||||
</router-link>
|
||||
<router-link to="/reports/operations">
|
||||
<CheckCircleOutlined />
|
||||
<span>运营报表</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
background: var(--fms-surface);
|
||||
|
||||
&__copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
strong {
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
color: var(--fms-text);
|
||||
font-size: 19px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: grid;
|
||||
flex: none;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
place-items: center;
|
||||
border-radius: 4px;
|
||||
font-size: 17px;
|
||||
|
||||
&--blue {
|
||||
background: #e6f4ff;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
&--amber {
|
||||
background: #fff7e6;
|
||||
color: #d46b08;
|
||||
}
|
||||
|
||||
&--green {
|
||||
background: #f0f9eb;
|
||||
color: #389e0d;
|
||||
}
|
||||
|
||||
&--gray {
|
||||
background: #f2f3f5;
|
||||
color: #4e5969;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
min-width: 0;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
background: var(--fms-surface);
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
min-height: 46px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 7px 10px;
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: var(--fms-text);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-panel :deep(.ant-table-wrapper) {
|
||||
padding: 0 2px 2px;
|
||||
}
|
||||
|
||||
.quick-links {
|
||||
display: grid;
|
||||
padding: 4px;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
min-height: 36px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 7px;
|
||||
border-radius: 3px;
|
||||
color: var(--fms-text);
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: var(--fms-hover);
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { PlusOutlined, ReloadOutlined, SearchOutlined } from "@antdv-next/icons";
|
||||
|
||||
const route = useRoute();
|
||||
const loading = ref(false);
|
||||
const rows = ref([]);
|
||||
const total = ref(0);
|
||||
const filters = reactive({ keyword: "" });
|
||||
const pagination = reactive({ current: 1, pageSize: 20 });
|
||||
const title = computed(() => route.meta.title);
|
||||
|
||||
const columns = computed(() => [
|
||||
{ title: "编号", dataIndex: "id", key: "id", width: 180 },
|
||||
{ title: "名称", dataIndex: "name", key: "name" },
|
||||
{ title: "状态", dataIndex: "state", key: "state", width: 120 },
|
||||
{ title: "更新时间", dataIndex: "updated_at", key: "updated_at", width: 180 },
|
||||
]);
|
||||
|
||||
function loadData() {
|
||||
loading.value = true;
|
||||
rows.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
filters.keyword = "";
|
||||
pagination.current = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
watch(() => route.meta.source, loadData, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="data-page">
|
||||
<section class="data-toolbar">
|
||||
<div class="data-toolbar__title">
|
||||
<h1>{{ title }}</h1>
|
||||
<span>共 {{ total }} 条</span>
|
||||
</div>
|
||||
<a-button type="primary">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
</section>
|
||||
|
||||
<section class="filter-bar">
|
||||
<a-input
|
||||
v-model:value="filters.keyword"
|
||||
class="keyword-input"
|
||||
placeholder="输入名称查询"
|
||||
allow-clear
|
||||
@press-enter="loadData"
|
||||
>
|
||||
<template #prefix><SearchOutlined /></template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="loadData">查询</a-button>
|
||||
<a-button @click="reset">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</section>
|
||||
|
||||
<section class="table-panel">
|
||||
<a-table
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:data-source="rows"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
showTotal: (value) => `共 ${value} 条`,
|
||||
}"
|
||||
:scroll="{ x: 760 }"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.data-page {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.data-toolbar,
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.data-toolbar {
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
color: var(--fms-text);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
|
||||
.keyword-input {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.table-panel {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { LockOutlined, UserOutlined } from "@antdv-next/icons";
|
||||
import { authApi } from "../services/api";
|
||||
import { useAuthStore } from "../stores/auth";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const form = reactive({
|
||||
orgid: "",
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
errorMessage.value = "";
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const session = await authApi.login(form);
|
||||
authStore.setSession(session, form.orgid);
|
||||
await router.replace(route.query.redirect || "/dashboard");
|
||||
} catch (error) {
|
||||
errorMessage.value = error.message || "登录失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="login-page">
|
||||
<section class="login-shell">
|
||||
<div class="login-brand">
|
||||
<div class="login-brand__mark">F</div>
|
||||
<div>
|
||||
<h1>FMS 管理系统</h1>
|
||||
<p>机构业务管理平台</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-form layout="vertical" :model="form" @finish="submit">
|
||||
<a-alert
|
||||
v-if="errorMessage"
|
||||
class="login-alert"
|
||||
type="error"
|
||||
show-icon
|
||||
:message="errorMessage"
|
||||
/>
|
||||
<a-form-item
|
||||
label="机构码"
|
||||
name="orgid"
|
||||
:rules="[{ required: true, message: '请输入机构码' }]"
|
||||
>
|
||||
<a-input v-model:value="form.orgid" size="large" autocomplete="organization" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="用户名"
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请输入用户名' }]"
|
||||
>
|
||||
<a-input v-model:value="form.username" size="large" autocomplete="username">
|
||||
<template #prefix><UserOutlined /></template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="密码"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请输入密码' }]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="form.password"
|
||||
size="large"
|
||||
autocomplete="current-password"
|
||||
>
|
||||
<template #prefix><LockOutlined /></template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-button type="primary" html-type="submit" size="large" block :loading="loading">
|
||||
登录
|
||||
</a-button>
|
||||
</a-form>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background:
|
||||
linear-gradient(90deg, var(--fms-border) 1px, transparent 1px),
|
||||
linear-gradient(var(--fms-border) 1px, transparent 1px), var(--fms-bg);
|
||||
background-size: 32px 32px;
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
width: min(100%, 400px);
|
||||
padding: 30px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 8px;
|
||||
background: var(--fms-surface);
|
||||
box-shadow: var(--fms-shadow);
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
|
||||
&__mark {
|
||||
display: grid;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
place-items: center;
|
||||
border-radius: 7px;
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
color: var(--fms-text);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 3px 0 0;
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-alert {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-page {
|
||||
padding: 0;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user