Initial commit

This commit is contained in:
南宁网宿科技
2024-04-24 16:36:46 +08:00
commit 121348e011
991 changed files with 158700 additions and 0 deletions
+247
View File
@@ -0,0 +1,247 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
:width="700"
:visible="visible"
:maskClosable="false"
:title="isUpdate ? '编辑应用' : '创建应用'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-descriptions :column="2">
<a-descriptions-item>
<a-form-item label="应用名称" name="appName">
<a-input
allow-clear
placeholder="请输入应用名称"
class="form-item"
v-model:value="form.appName"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<a-form-item label="项目类型" name="appType">
<DictSelect
dict-code="appType"
class="form-item"
placeholder="请选择项目类型"
v-model:value="form.appType"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<a-form-item label="应用标识" name="appCode">
<a-input
allow-clear
placeholder="请输入应用标识(英文字母)"
class="form-item"
v-model:value="form.appCode"
@input="changeAppCode"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<a-form-item label="企业名称" name="companyName">
<SelectCompany
:placeholder="`所属企业`"
class="form-item"
v-model:value="form.companyName"
@done="chooseCompanyName"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<a-form-item label="关联租户" name="tenantCode">
<a-input
allow-clear
placeholder="请输入关联的租户ID"
class="form-item"
v-model:value="form.tenantCode"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item />
<a-descriptions-item :span="2">
<a-form-item label="应用描述" name="comments" class="ele-fluid">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入应用描述"
v-model:value="form.comments"
/>
</a-form-item>
</a-descriptions-item>
</a-descriptions>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, isChinese } from 'ele-admin-pro';
import { addApp, updateApp } from '@/api/oa/app';
import type { App } from '@/api/oa/app/model';
import { FormInstance, RuleObject } from 'ant-design-vue/es/form';
import { Company } from '@/api/system/company/model';
import { pageCompany } from '@/api/system/company';
const useForm = Form.useForm;
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: App | null;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否是修改
const isUpdate = ref(false);
const formRef = ref<FormInstance | null>(null);
// 表单数据
const form = reactive<App>({
appId: undefined,
appName: '',
appCode: '',
appType: undefined,
companyName: '',
tenantCode: '',
comments: '',
appStatus: '开发中'
});
// 表单验证规则
const rules = reactive({
appName: [
{
required: true,
type: 'string',
message: '请输入应用名称',
trigger: 'blur'
}
],
// companyName: [
// {
// required: true,
// type: 'string',
// message: '请选择所属企业',
// trigger: 'blur'
// }
// ],
appCode: [
{
required: true,
type: 'string',
message: '请输入应用标识(英文字母)',
trigger: 'blur',
validator: async (_rule: RuleObject, value: string) => {
if (isChinese(value)) {
return Promise.reject('请输入正确的应用标识');
}
return Promise.resolve();
}
}
],
appType: [
{
required: true,
message: '请选择项目类型'
}
]
});
const { resetFields } = useForm(form, rules);
const chooseCompanyName = (data: Company) => {
form.appUrl = data.domain;
form.companyName = data.companyName;
form.companyId = data.companyId;
form.tenantId = data.tenantId;
};
const changeAppCode = () => {
form.packageName = `com.gxwebsoft.${form.appCode}`;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateApp : addApp;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
const reload = () => {
loading.value = true;
// 加载企业信息
const companyId = props.data?.companyId;
pageCompany({ companyId }).then((res) => {
form.tenantId = undefined;
const list = res?.list;
if (list && list.length > 0) {
form.tenantId = list[0].tenantId;
}
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignObject(form, props.data);
if (props.data.search) {
form.search = props.data.search == 1 ? true : 0;
}
isUpdate.value = true;
reload();
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
}
);
</script>
<style lang="less">
.form-item {
min-width: 300px;
}
</style>
+259
View File
@@ -0,0 +1,259 @@
<template>
<a-card
title="续费管理"
:bordered="false"
v-if="hasRole('superAdmin') || hasRole('admin')"
>
<template #extra>
<a-button @click="addRecord">添加</a-button>
</template>
<div class="content">
<table
class="ele-table ele-table-border ele-table-stripe ele-table-medium"
>
<thead>
<tr>
<th>状态</th>
<th>续费金额</th>
<th>开始时间</th>
<th>结束时间</th>
<th width="360">描述</th>
<th style="text-align: center">操作</th>
</tr>
</thead>
<tr v-for="(item, index) in list" :key="index">
<td>
<template v-if="item.editStatus">
<a-select placeholder="请选择" v-model:value="item.status">
<a-select-option :value="0">待缴费</a-select-option>
<a-select-option :value="1">已缴费</a-select-option>
</a-select>
</template>
<template v-else>
<span v-if="item.status == 1">已缴费</span>
<span v-if="item.status == 0">待缴费</span>
</template>
</td>
<td>
<template v-if="item.editStatus">
<a-input-number
:min="0"
:max="999999"
class="ele-fluid"
placeholder="请输入续费金额"
v-model:value="item.money"
/>
</template>
<template v-else> ¥{{ item.money }} </template>
</td>
<td>
<template v-if="item.editStatus">
<a-date-picker
placeholder="开始日期"
value-format="YYYY-MM-DD"
v-model:value="item.startTime"
/>
</template>
<template v-else>
{{ toDateString(item.startTime, 'yyyy-MM-dd') }}
</template>
</td>
<td>
<template v-if="item.editStatus">
<a-date-picker
placeholder="结束日期"
value-format="YYYY-MM-DD"
v-model:value="item.endTime"
/>
</template>
<template v-else>
{{ toDateString(item.endTime, 'yyyy-MM-dd') }}
</template>
</td>
<td>
<template v-if="item.editStatus">
<a-input
class="ele-fluid"
placeholder="请输入描述内容"
v-model:value="item.comments"
/>
<a-upload
v-model:file-list="item.images"
class="upload-list-inline"
list-type="picture"
:before-upload="beforeUpload"
>
<a-button>
<UploadOutlined />
上传附件
</a-button>
</a-upload>
</template>
<template v-else>
<div class="comments">{{ item.comments }}</div>
<div class="files">
<a-upload
v-model:file-list="item.images"
class="upload-list-inline"
>
</a-upload>
</div>
</template>
</td>
<td style="text-align: center">
<template v-if="item.editStatus">
<a-space>
<a @click="save(item, index)">保存</a>
<a-divider type="vertical" />
<a-popconfirm title="确定要删除此记录吗?" @confirm="remove(index)">
<a class="ele-text-info">删除</a>
</a-popconfirm>
</a-space>
</template>
<template v-else>
<a-space>
<a @click="openEdit(index)">编辑</a>
<a-divider type="vertical" v-if="hasRole('superAdmin')" @confirm="removeRel(index)" />
<a-popconfirm title="确定要删除此记录吗?" v-if="hasRole('superAdmin')" @confirm="removeRel(index)">
<a class="ele-text-info">删除</a>
</a-popconfirm>
</a-space>
</template>
</td>
</tr>
</table>
</div>
</a-card>
</template>
<script setup lang="ts">
import { hasRole } from '@/utils/permission';
import { ref, watch } from 'vue';
import { addAppRenew, pageAppRenew, removeAppRenew } from "@/api/oa/app/renew";
import { AppRenew } from '@/api/oa/app/renew/model';
import { useUserStore } from '@/store/modules/user';
import { message } from 'ant-design-vue';
import { toDateString } from 'ele-admin-pro';
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
import { uploadOss } from "@/api/system/file";
import { isImage } from "@/utils/common";
const props = defineProps<{
editStatus?: boolean;
appId?: number;
companyId?: number;
}>();
const userStore = useUserStore();
const list = ref<AppRenew[]>([]);
const files = ref<ItemType[]>([]);
const addRecord = () => {
list.value.unshift({
money: undefined,
comments: undefined,
startTime: undefined,
endTime: undefined,
nickname: userStore.info?.nickname,
status: 0,
images: undefined,
editStatus: true
});
};
// 文件上传事件
const beforeUpload = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
}
if (!file.type.startsWith("image")) {
if (file.size / 1024 / 1024 > 100) {
message.error("大小不能超过 100MB");
return;
}
}
onUpload(item);
return false;
};
const onUpload = (d: ItemType) => {
uploadOss(<File>d.file)
.then((result) => {
files.value.push({
uid: result.id,
url: result.path,
name: isImage(result.path) ? 'image' : result.name,
status: "done"
});
message.success("上传成功");
})
.catch((e) => {
message.error(e.message);
});
};
const remove = (index: number) => {
list.value.splice(index, 1);
};
const openEdit = (index: number) => {
list.value[index].editStatus = true
}
const removeRel = (index: number) => {
removeAppRenew(list.value[index].appRenewId).then(() => {
list.value.splice(index, 1);
message.success('删除成功')
})
}
const save = (item: AppRenew, index: number) => {
item.appId = props.appId;
item.companyId = props.companyId;
item.startTime = item.startTime ? item.startTime + ' 00:00:00' : '';
item.endTime = item.endTime ? item.endTime + ' 23:59:59' : '';
item.nickname = userStore.info?.nickname;
item.userId = userStore.info?.userId;
if(files.value.length > 0){
item.images = JSON.stringify(files.value)
}
if (item.money == undefined || item.money == 0) {
message.error('请填写金额');
return false;
}
addAppRenew(item).then(() => {
list.value[index].editStatus = false;
message.success('保存成功');
});
};
const reload = () => {
pageAppRenew({ appId: props.appId }).then((res) => {
if (res?.list) {
list.value = res?.list.map((d) => {
d.editStatus = false;
if(d.images){
d.images = JSON.parse(d.images);
}
return d;
});
}
});
};
watch(
() => props.appId,
(options) => {
if (options) {
reload();
}
},
{
immediate: true
}
);
</script>
<style scoped lang="less"></style>
+179
View File
@@ -0,0 +1,179 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>创建</span>
</a-button>
<a-button
danger
type="primary"
class="ele-btn-icon"
:disabled="selection?.length === 0"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
<a-radio-group v-model:value="type" @change="handleSearch">
<a-radio-button value="全部">全部({{ conut.totalNum }})</a-radio-button>
<a-radio-button value="开发中"
>开发中({{ conut.totalNum2 }})</a-radio-button
>
<a-radio-button value="已上架"
>已上架({{ conut.totalNum3 }})</a-radio-button
>
<a-radio-button value="已下架"
>已下架({{ conut.totalNum5 }})</a-radio-button
>
<a-radio-button value="未签续费"
>未签续费({{ conut.totalNum4 }})</a-radio-button
>
<a-radio-button value="客户案例"
>客户案例({{ conut.totalNum6 }})</a-radio-button
>
<a-radio-button value="自营产品"
>自营产品({{ conut.totalNum7 }})</a-radio-button
>
</a-radio-group>
<!-- <SelectCompany-->
<!-- :placeholder="`按企业筛选`"-->
<!-- v-model:value="where.companyName"-->
<!-- @done="chooseCompanyName"-->
<!-- />-->
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="where.keywords"
@pressEnter="search"
@search="search"
/>
<a-button @click="reset">重置</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import useSearch from '@/utils/use-search';
import { ref, watch } from 'vue';
import { AppParam } from '@/api/oa/app/model';
import { Company } from '@/api/system/company/model';
import { getCount } from '@/api/oa/app';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: AppParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'advanced'): void;
}>();
// 下来选项
// 日期范围选择
const dateRange = ref<[string, string]>(['', '']);
const type = ref<any>('开发中');
// 表单数据
const { where, resetFields } = useSearch<AppParam>({
appId: undefined,
userId: undefined,
appStatus: undefined,
companyId: undefined,
companyName: '',
keywords: ''
});
// 统计数据
const conut = ref({
totalNum: 0,
totalNum2: 0,
totalNum3: 0,
totalNum4: 0,
totalNum5: 0,
totalNum6: 0,
totalNum7: 0
});
/* 搜索 */
const search = () => {
const [d1, d2] = dateRange.value ?? [];
emit('search', {
...where,
createTimeStart: d1 ? d1 + ' 00:00:00' : '',
createTimeEnd: d2 ? d2 + ' 23:59:59' : ''
});
};
// 发布应用
const add = () => {
emit('add');
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
// const chooseCompanyName = (data: Company) => {
// where.companyName = data.companyName;
// where.companyId = data.companyId;
// emit('search', where);
// };
const handleSearch = () => {
where.showExpiration = undefined;
where.appStatus = type.value;
if (type.value == '全部') {
where.appStatus = undefined;
where.showExpiration = undefined;
} else {
where.appStatus = type.value;
if (where.appStatus == '客户案例') {
where.showCase = true;
where.appStatus = undefined;
where.showIndex = undefined;
}
if (where.appStatus == '自营产品') {
where.showIndex = true;
where.appStatus = undefined;
where.showCase = undefined;
}
if (type.value == '未签续费') {
where.sort = 'expirationTime';
where.order = 'asc';
where.showExpiration = false;
where.appStatus = '已上架';
} else {
where.sort = undefined;
where.order = undefined;
}
}
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
getCount().then((data: any) => {
conut.value = data;
});
watch(
() => props.selection,
() => {}
);
</script>
+152
View File
@@ -0,0 +1,152 @@
<template>
<a-card
title="待处理工单"
:bordered="false"
v-if="hasRole('superAdmin') || hasRole('admin')"
>
<template #extra>
<a href="/oa/task">查看更多</a>
</template>
<div class="content">
<a-space style="margin-bottom: 15px" v-if="editStatus" />
<table class="ele-table ele-table-stripe ele-table-medium">
<thead>
<tr>
<th width="80">工单号</th>
<th width="100">工单类型</th>
<th>问题描述</th>
<th width="150">创建时间</th>
<th width="100">状态</th>
</tr>
</thead>
<tbody v-for="(item, index) in list" :key="index">
<tr>
<td>{{ item.taskId }}</td>
<td>{{ item.taskType }}</td>
<td>
<div class="user-box">
<div class="user-info">
<a-badge
:dot="!item.isRead && item.userId != userStore.info?.userId"
>
<a-avatar :src="item.lastAvatar" size="large" />
</a-badge>
</div>
<div
class="content"
style="display: flex; flex-direction: column"
>
<div class="nickname">
<span class="ele-text-secondary">{{
item.lastNickname ? item.lastNickname : '未知'
}}</span>
<span
class="ele-text-placeholder"
style="padding-left: 10px"
>{{ timeAgo(item.updateTime) }}</span
>
</div>
<div class="text">
<a
class="ele-text-heading"
@click="openUrl('/oa/task/detail/' + item.taskId)"
>{{ item.content }}</a
>
</div>
</div>
<div class="last-time ele-text-info"> </div>
</div>
</td>
<td>{{ toDateString(item.createTime, 'MM月dd日 HH:mm') }}</td>
<td>
<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>
</td>
</tr>
</tbody>
</table>
</div>
</a-card>
</template>
<script setup lang="ts">
import { hasRole } from '@/utils/permission';
import { ref, watch } from 'vue';
import { pageTask } from '@/api/oa/task';
import { Task } from '@/api/oa/task/model';
import {
CLOSED,
COMPLETED,
PENDING,
PROCESSING,
TOBEARRANGED,
TOBECONFIRMED
} from '@/api/oa/task/model/progress';
import { openUrl } from '@/utils/common';
import { timeAgo, toDateString } from 'ele-admin-pro';
import { useUserStore } from '@/store/modules/user';
const props = defineProps<{
editStatus?: boolean;
appId?: number;
companyId?: number;
}>();
const list = ref<Task[]>([]);
const userStore = useUserStore();
const reload = () => {
pageTask({ appId: props.appId }).then((res) => {
if (res?.list) {
list.value = res?.list;
}
});
};
watch(
() => props.appId,
(options) => {
if (options) {
reload();
}
},
{
immediate: true
}
);
</script>
<style scoped lang="less">
.user-box {
display: flex;
align-items: center;
.user-info {
display: flex;
flex-direction: column;
align-items: start;
margin-right: 7px;
}
.last-time {
margin-left: 12px;
}
.content {
.text {
max-width: 90%;
}
}
}
</style>