Initial commit
This commit is contained in:
138
src/views/oa/dashboard/components/activities-card.vue
Normal file
138
src/views/oa/dashboard/components/activities-card.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<!-- 最新动态 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
style="height: 346px; padding: 22px 20px 0 20px"
|
||||
class="ele-scrollbar-hover"
|
||||
>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="item in activities"
|
||||
:key="item.id"
|
||||
:color="item.color"
|
||||
>
|
||||
<em>{{ item.time }}</em>
|
||||
<em>{{ item.title }}</em>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
interface Activitie {
|
||||
id: number;
|
||||
title: string;
|
||||
time: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 最新动态数据
|
||||
const activities = ref<Activitie[]>([]);
|
||||
|
||||
/* 查询最新动态 */
|
||||
const queryActivities = () => {
|
||||
activities.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'SunSmile 解决了bug 登录提示操作失败',
|
||||
time: '20:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Jasmine 解决了bug 按钮颜色与设计不符',
|
||||
time: '19:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '项目经理 指派了任务 解决项目一的bug',
|
||||
time: '18:30'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '项目经理 指派了任务 解决项目二的bug',
|
||||
time: '17:30'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '项目经理 指派了任务 解决项目三的bug',
|
||||
time: '16:30'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '项目经理 指派了任务 解决项目四的bug',
|
||||
time: '15:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '项目经理 指派了任务 解决项目五的bug',
|
||||
time: '14:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '项目经理 指派了任务 解决项目六的bug',
|
||||
time: '12:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: '项目经理 指派了任务 解决项目七的bug',
|
||||
time: '11:30'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: '项目经理 指派了任务 解决项目八的bug',
|
||||
time: '10:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: '项目经理 指派了任务 解决项目九的bug',
|
||||
time: '09:30',
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: '项目经理 指派了任务 解决项目十的bug',
|
||||
time: '08:30',
|
||||
color: 'red'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryActivities();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-scrollbar-hover
|
||||
:deep(.ant-timeline-item-last > .ant-timeline-item-content) {
|
||||
min-height: auto;
|
||||
}
|
||||
</style>
|
||||
91
src/views/oa/dashboard/components/app-list.vue
Normal file
91
src/views/oa/dashboard/components/app-list.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<a-card
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
:body-style="{
|
||||
padding: '2px',
|
||||
minHeight: '252px',
|
||||
maxHeight: '252px',
|
||||
overflow: 'hidden'
|
||||
}"
|
||||
>
|
||||
<template #extra
|
||||
><a @click="openUrl('/oa/app/index')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="app-box">
|
||||
<a-image
|
||||
:height="45"
|
||||
:width="45"
|
||||
:preview="false"
|
||||
:src="item.appIcon"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="app-info">
|
||||
<a
|
||||
class="ele-text-heading"
|
||||
@click="openUrl('/oa/app/detail/' + item.appId)"
|
||||
>
|
||||
{{ item.appName }}
|
||||
</a>
|
||||
<span class="ele-text-placeholder">
|
||||
{{ item.appCode }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Article } from '@/api/cms/article/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { pageApp } from '@/api/oa/app';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const list = ref<Article[]>([]);
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { title } = props;
|
||||
// 加载文章列表
|
||||
pageApp({
|
||||
limit: 5,
|
||||
status: 0,
|
||||
appStatus: '开发中'
|
||||
}).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
src/views/oa/dashboard/components/article-list.vue
Normal file
56
src/views/oa/dashboard/components/article-list.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px', minHeight: '252px' }">
|
||||
<template #extra
|
||||
><a
|
||||
@click="openNew('/cms/category/' + categoryId)"
|
||||
class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a
|
||||
class="ele-text-heading"
|
||||
@click="openUrl('/cms/article/' + item.articleId)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { pageArticle } from '@/api/cms/article';
|
||||
import { Article } from '@/api/cms/article/model';
|
||||
import { openNew, openUrl } from '@/utils/common';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
const list = ref<Article[]>([]);
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
categoryId: number;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { categoryId } = props;
|
||||
// 加载文章列表
|
||||
pageArticle({ categoryId, limit: 6 }).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
80
src/views/oa/dashboard/components/count-user.vue
Normal file
80
src/views/oa/dashboard/components/count-user.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="小组成员">
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>邀请加入</template>
|
||||
<UserAddOutlined @click="onShowQrcode" :style="{ fontSize: '18px' }" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-list
|
||||
class="demo-loadmore-list"
|
||||
item-layout="horizontal"
|
||||
:data-source="list"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a-popover>
|
||||
<template #content> 待处理 </template>
|
||||
<a class="ele-text-danger">{{ item.pending }}</a>
|
||||
</a-popover>
|
||||
<a-popover>
|
||||
<template #content> 本月已处理 </template>
|
||||
<a class="ele-text-secondary">{{ item.month }}</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<a-skeleton avatar :title="false" :loading="!!item.loading" active>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
{{ item.nickname }}
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.avatar" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-skeleton>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<!-- 工单二维码 -->
|
||||
<Qrcode v-model:visible="showQrcode" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { pageTaskCount } from '@/api/oa/task-count';
|
||||
import { TaskCount } from '@/api/oa/task-count/model';
|
||||
import { UserAddOutlined } from '@ant-design/icons-vue';
|
||||
import Qrcode from './qrcode.vue';
|
||||
|
||||
const showQrcode = ref(false);
|
||||
const list = ref<TaskCount[]>([]);
|
||||
|
||||
pageTaskCount({ limit: 10, roleCode: 'commander' }).then((res) => {
|
||||
if (res) {
|
||||
list.value = res?.list;
|
||||
}
|
||||
});
|
||||
|
||||
const onShowQrcode = () => {
|
||||
showQrcode.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-evaluate-text {
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0.8;
|
||||
|
||||
& > .anticon {
|
||||
font-size: 12px;
|
||||
margin: 0 6px 0 8px;
|
||||
}
|
||||
}
|
||||
/deep/.ant-list-item {
|
||||
padding: 7px 0;
|
||||
}
|
||||
</style>
|
||||
70
src/views/oa/dashboard/components/docs.vue
Normal file
70
src/views/oa/dashboard/components/docs.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
src/views/oa/dashboard/components/goal-card.vue
Normal file
70
src/views/oa/dashboard/components/goal-card.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
181
src/views/oa/dashboard/components/link-card.vue
Normal file
181
src/views/oa/dashboard/components/link-card.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<!-- 快捷方式 -->
|
||||
<template>
|
||||
<a-row :gutter="16" ref="wrapRef">
|
||||
<a-col v-for="item in data" :key="item.url" :lg="3" :md="6" :sm="9" :xs="8">
|
||||
<a-card :bordered="false" hoverable :body-style="{ padding: 0 }">
|
||||
<div class="app-link-block" @click="navTo(item)">
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="app-link-icon"
|
||||
:style="{ color: item.color }"
|
||||
/>
|
||||
<div class="app-link-title">{{ item.title }}</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import SortableJs from 'sortablejs';
|
||||
import type { Row as ARow } from 'ant-design-vue';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { getOriginDomain } from '@/utils/domain';
|
||||
const CACHE_KEY = 'workplace-links';
|
||||
// 当前开发环境
|
||||
const env = import.meta.env.MODE;
|
||||
|
||||
interface LinkItem {
|
||||
icon: string;
|
||||
title: string;
|
||||
url: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 默认顺序
|
||||
const DEFAULT: LinkItem[] = [
|
||||
{
|
||||
icon: 'settingOutlined',
|
||||
title: '系统设置',
|
||||
url: '/system/profile'
|
||||
},
|
||||
{
|
||||
icon: 'AntDesignOutlined',
|
||||
title: '项目管理系统',
|
||||
url: '/oa/app/index'
|
||||
},
|
||||
{
|
||||
icon: 'CheckSquareOutlined',
|
||||
title: '工单管理系统',
|
||||
url: '/oa/task/index'
|
||||
},
|
||||
{
|
||||
icon: 'GatewayOutlined',
|
||||
title: '资产管理系统',
|
||||
url: '/assets/server'
|
||||
},
|
||||
{
|
||||
icon: 'RedditOutlined',
|
||||
title: '客户管理系统',
|
||||
url: '/system/company'
|
||||
},
|
||||
// {
|
||||
// icon: 'ShoppingOutlined',
|
||||
// title: '产品管理',
|
||||
// url: '/product/index'
|
||||
// },
|
||||
// {
|
||||
// icon: 'FileSearchOutlined',
|
||||
// title: '文章管理',
|
||||
// url: '/cms/article'
|
||||
// },
|
||||
{
|
||||
icon: 'ChromeOutlined',
|
||||
title: '网址导航',
|
||||
url: '/oa/link'
|
||||
},
|
||||
{
|
||||
icon: 'AppstoreAddOutlined',
|
||||
title: '扩展插件',
|
||||
url: '/system/plug'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取缓存的顺序
|
||||
const cache = (() => {
|
||||
const str = localStorage.getItem(CACHE_KEY);
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]);
|
||||
|
||||
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
|
||||
|
||||
let sortableIns: SortableJs | null = null;
|
||||
|
||||
/* 重置布局 */
|
||||
const reset = () => {
|
||||
data.value = [...DEFAULT];
|
||||
cacheData();
|
||||
};
|
||||
|
||||
/* 缓存布局 */
|
||||
const cacheData = () => {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
|
||||
};
|
||||
|
||||
const navTo = (item) => {
|
||||
if (item.icon == 'DesktopOutlined') {
|
||||
if (env == 'development') {
|
||||
return openUrl(getOriginDomain());
|
||||
}
|
||||
return openUrl(`http://www.${domain.value}`);
|
||||
}
|
||||
openUrl(item.url);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
sortableIns = new SortableJs(wrapRef.value?.$el, {
|
||||
animation: 300,
|
||||
onUpdate: ({ oldIndex, newIndex }) => {
|
||||
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
|
||||
const temp = [...data.value];
|
||||
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
|
||||
data.value = temp;
|
||||
cacheData();
|
||||
}
|
||||
},
|
||||
setData: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sortableIns) {
|
||||
sortableIns.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ reset });
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from './link-icons';
|
||||
import { getSiteInfo } from '@/api/layout';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const tenantId = ref<number>();
|
||||
const domain = ref<string>();
|
||||
|
||||
getSiteInfo().then((data) => {
|
||||
tenantId.value = data.tenantId;
|
||||
domain.value = data.domain;
|
||||
});
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-link-block {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: inherit;
|
||||
|
||||
.app-link-icon {
|
||||
color: #666666;
|
||||
font-size: 30px;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/views/oa/dashboard/components/link-icons.ts
Normal file
15
src/views/oa/dashboard/components/link-icons.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
FileSearchOutlined,
|
||||
ChromeOutlined,
|
||||
ShoppingOutlined,
|
||||
LaptopOutlined,
|
||||
AppstoreAddOutlined,
|
||||
DesktopOutlined,
|
||||
AntDesignOutlined,
|
||||
SettingOutlined,
|
||||
CheckSquareOutlined,
|
||||
GatewayOutlined,
|
||||
RedditOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
54
src/views/oa/dashboard/components/link-list.vue
Normal file
54
src/views/oa/dashboard/components/link-list.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<a-card :title="linkType" :bordered="false" :body-style="{ padding: '2px', minHeight: '252px' }">
|
||||
<template #extra
|
||||
><a @click="openNew('/oa/link')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.icon" />
|
||||
</template>
|
||||
<template #title>
|
||||
<a @click="openUrl(item.url)">{{ item.name }}</a>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { openNew, openUrl } from '@/utils/common';
|
||||
import { pageLink } from '@/api/oa/link';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
const props = defineProps<{
|
||||
linkType: string;
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { linkType } = props;
|
||||
// 加载文章列表
|
||||
pageLink({ linkType, limit: 5 }).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
38
src/views/oa/dashboard/components/more-icon.vue
Normal file
38
src/views/oa/dashboard/components/more-icon.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<a-dropdown placement="bottomRight">
|
||||
<more-outlined class="ele-text-secondary" style="font-size: 18px" />
|
||||
<template #overlay>
|
||||
<a-menu :selectable="false" @click="onClick">
|
||||
<a-menu-item key="edit">
|
||||
<div class="ele-cell">
|
||||
<edit-outlined />
|
||||
<div class="ele-cell-content">编辑</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="remove">
|
||||
<div class="ele-cell ele-text-danger">
|
||||
<delete-outlined />
|
||||
<div class="ele-cell-content">删除</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
MoreOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'edit'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const onClick = ({ key }) => {
|
||||
emit(key);
|
||||
};
|
||||
</script>
|
||||
121
src/views/oa/dashboard/components/profile-card.vue
Normal file
121
src/views/oa/dashboard/components/profile-card.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<!-- 用户信息 -->
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: '20px' }">
|
||||
<div class="ele-cell workplace-user-card">
|
||||
<div class="ele-cell-content ele-cell">
|
||||
<a-avatar :size="68" :src="loginUser.avatar">
|
||||
<template v-if="!loginUser.avatar" #icon>
|
||||
<user-outlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<div class="ele-cell-content">
|
||||
<h4 class="ele-elip">
|
||||
早安, {{ loginUser.nickname }}, 开始您一天的工作吧!
|
||||
</h4>
|
||||
<div class="ele-elip ele-text-secondary">
|
||||
<cloud-outlined />
|
||||
<em>{{ elip[Math.floor(Math.random() * elip.length)] }}</em>
|
||||
<!-- <em>今日多云转阴,18℃ - 22℃,出门记得穿外套哦~</em>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workplace-count-group">
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="blue" shape="circle" size="small">-->
|
||||
<!-- <appstore-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">项目数</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="orange" shape="circle" size="small">-->
|
||||
<!-- <check-square-outlined />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">待办项</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>6 / 24</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="green" shape="circle" size="small">-->
|
||||
<!-- <bell-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">消息</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
CloudOutlined,
|
||||
AppstoreFilled,
|
||||
CheckSquareOutlined,
|
||||
BellFilled
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 当前登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
const elip = ref<string[]>([
|
||||
'小事成就大事,细节成就完美~',
|
||||
'心态决定命运,自信走向成功',
|
||||
'人生能有几回博,今日不博何时博',
|
||||
'成功需要成本,时间也是一种成本,对时间的珍惜就是对成本的节约',
|
||||
'有志者自有千方百计,无志者只感千难万难',
|
||||
'积一时之跬步,臻千里之遥程'
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-user-card {
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workplace-count-item {
|
||||
display: inline-block;
|
||||
margin: 0 4px 0 24px;
|
||||
}
|
||||
|
||||
.workplace-count-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.workplace-count-item {
|
||||
margin: 0 2px 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.workplace-user-card {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
66
src/views/oa/dashboard/components/qrcode.vue
Normal file
66
src/views/oa/dashboard/components/qrcode.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="400"
|
||||
:visible="visible"
|
||||
:title="`邀请新成员`"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
@ok="save"
|
||||
>
|
||||
<div
|
||||
class="qrcode-list"
|
||||
style="display: flex; justify-content: space-around"
|
||||
>
|
||||
<div>
|
||||
<img :src="qrcode" width="240" height="240" />
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
padding-top: 20px;
|
||||
"
|
||||
>使用微信扫一扫</div
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { User } from '@/api/system/user/model';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { taskJoinQRCode } from '@/api/oa/task';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', user: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const qrcode = ref('');
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<User>({
|
||||
userId: undefined,
|
||||
nickname: undefined
|
||||
});
|
||||
|
||||
const save = () => {
|
||||
emit('done', form);
|
||||
};
|
||||
|
||||
taskJoinQRCode({}).then((text) => {
|
||||
qrcode.value = String(text);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
116
src/views/oa/dashboard/components/task-card.vue
Normal file
116
src/views/oa/dashboard/components/task-card.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<a-card
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: '2px', minHeight: '252px' }"
|
||||
>
|
||||
<template #extra
|
||||
><a @click="openUrl('/oa/task')" class="ele-text-placeholder"
|
||||
>更多<RightOutlined /></a
|
||||
></template>
|
||||
<a-list :size="`small`" :split="false" :data-source="list">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="app-box">
|
||||
<div class="app-info">
|
||||
<a
|
||||
class="ele-text-secondary"
|
||||
@click="openNew('/oa/task/detail/' + item.taskId)"
|
||||
>
|
||||
<a-typography-paragraph
|
||||
ellipsis
|
||||
:content="`【${item.taskType}】${item.name}`"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<a class="ele-text-placeholder">
|
||||
<a-tag v-if="item.progress === TOBEARRANGED" color="red"
|
||||
>待安排</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === PENDING" color="orange"
|
||||
>待处理</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === PROCESSING" color="purple"
|
||||
>处理中</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === TOBECONFIRMED" color="cyan"
|
||||
>待评价</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === COMPLETED" color="green"
|
||||
>已完成</a-tag
|
||||
>
|
||||
<a-tag v-if="item.progress === CLOSED">已关闭</a-tag>
|
||||
<div class="ele-text-danger" v-if="item.overdueDays">
|
||||
已逾期{{ item.overdueDays }}天
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { Task } from '@/api/oa/task/model';
|
||||
import { pageTask } from '@/api/oa/task';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {
|
||||
CLOSED,
|
||||
COMPLETED,
|
||||
PENDING,
|
||||
PROCESSING,
|
||||
TOBEARRANGED,
|
||||
TOBECONFIRMED
|
||||
} from '@/api/oa/task/model/progress';
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const list = ref<Task[]>([]);
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
const reload = () => {
|
||||
const { title } = props;
|
||||
const userStore = useUserStore();
|
||||
const where = {
|
||||
userId: undefined,
|
||||
commander: userStore.info?.userId,
|
||||
limit: 6,
|
||||
status: 0
|
||||
};
|
||||
// 加载列表
|
||||
pageTask(where).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardArticleList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
flex-direction: column;
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
src/views/oa/dashboard/components/user-list.vue
Normal file
84
src/views/oa/dashboard/components/user-list.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<!-- 小组成员 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
v-for="(item, index) in userList"
|
||||
:key="index"
|
||||
class="ele-cell user-list-item"
|
||||
>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-avatar :size="46" :src="item.avatar" />
|
||||
</div>
|
||||
<div class="ele-cell-content">
|
||||
<span class="ele-cell-title ele-elip">{{ item.nickname }}</span>
|
||||
<div class="ele-cell-desc ele-elip">{{ item.phone }}</div>
|
||||
</div>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-tag :color="['green', 'red'][item.status]">
|
||||
{{ ['在线', '离线'][item.status] }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
// 小组成员数据
|
||||
const userList = ref<User[]>([]);
|
||||
|
||||
/* 查询小组成员 */
|
||||
const queryUserList = () => {
|
||||
pageUsers({ parentId: 11, limit: 5 }).then((data: any) => {
|
||||
userList.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryUserList();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-list-item {
|
||||
padding: 12px 18px;
|
||||
|
||||
& + .user-list-item {
|
||||
border-top: 1px solid hsla(0, 0%, 60%, 0.15);
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ele-cell-desc {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user