更新backend相关问题
This commit is contained in:
parent
b727bae790
commit
cffc718284
@ -4,7 +4,7 @@ import request from "@/utils/request";
|
||||
// 获取文章列表
|
||||
export function listArticles(params) {
|
||||
return request({
|
||||
url: `/admin/articlesList`,
|
||||
url: `/backend/articlesList`,
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
@ -13,7 +13,7 @@ export function listArticles(params) {
|
||||
// 获取文章所有文章
|
||||
export function listAllArticles(params) {
|
||||
return request({
|
||||
url: `/admin/allarticles`,
|
||||
url: `/backend/allarticles`,
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
@ -22,7 +22,7 @@ export function listAllArticles(params) {
|
||||
// 获取文章详情
|
||||
export function getArticle(id) {
|
||||
return request({
|
||||
url: `/admin/articles/${id}`,
|
||||
url: `/backend/articles/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
@ -30,7 +30,7 @@ export function getArticle(id) {
|
||||
// 创建文章
|
||||
export function createArticle(data) {
|
||||
return request({
|
||||
url: '/admin/createarticle',
|
||||
url: '/backend/createarticle',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
@ -39,7 +39,7 @@ export function createArticle(data) {
|
||||
// 编辑文章
|
||||
export function editArticle(id, data) {
|
||||
return request({
|
||||
url: `/admin/editarticle/${id}`,
|
||||
url: `/backend/editarticle/${id}`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
@ -48,7 +48,7 @@ export function editArticle(id, data) {
|
||||
// 删除文章
|
||||
export function deleteArticle(id) {
|
||||
return request({
|
||||
url: `/admin/deletearticle/${id}`,
|
||||
url: `/backend/deletearticle/${id}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
@ -56,7 +56,7 @@ export function deleteArticle(id) {
|
||||
// 发布文章
|
||||
export function publishArticle(id,uid) {
|
||||
return request({
|
||||
url: `/admin/publisharticle/${id}`,
|
||||
url: `/backend/publisharticle/${id}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
uid
|
||||
@ -67,7 +67,7 @@ export function publishArticle(id,uid) {
|
||||
// 下架文章
|
||||
export function unPublishArticle(id) {
|
||||
return request({
|
||||
url: `/admin/unPublisharticle/${id}`,
|
||||
url: `/backend/unPublisharticle/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@ -75,7 +75,7 @@ export function unPublishArticle(id) {
|
||||
// 文章推荐
|
||||
export function articleRecommend(id) {
|
||||
return request({
|
||||
url: `/admin/articleRecommend/${id}`,
|
||||
url: `/backend/articleRecommend/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@ -83,7 +83,7 @@ export function articleRecommend(id) {
|
||||
// 取消文章推荐
|
||||
export function unArticleRecommend(id) {
|
||||
return request({
|
||||
url: `/admin/unArticleRecommend/${id}`,
|
||||
url: `/backend/unArticleRecommend/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@ -91,7 +91,7 @@ export function unArticleRecommend(id) {
|
||||
// 文章置顶
|
||||
export function articleTop(id) {
|
||||
return request({
|
||||
url: `/admin/articleTop/${id}`,
|
||||
url: `/backend/articleTop/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@ -99,7 +99,7 @@ export function articleTop(id) {
|
||||
// 取消文章置顶
|
||||
export function unArticleTop(id) {
|
||||
return request({
|
||||
url: `/admin/unArticleTop/${id}`,
|
||||
url: `/backend/unArticleTop/${id}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@ -111,7 +111,7 @@ export function unArticleTop(id) {
|
||||
// 获取所有分类列表
|
||||
export function allCategories(params) {
|
||||
return request({
|
||||
url: `/admin/allcategories`,
|
||||
url: `/backend/allcategories`,
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
@ -120,7 +120,7 @@ export function allCategories(params) {
|
||||
// 获取分类列表
|
||||
export function listCategories(params) {
|
||||
return request({
|
||||
url: `/admin/categories`,
|
||||
url: `/backend/categories`,
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
@ -129,7 +129,7 @@ export function listCategories(params) {
|
||||
// 获取分类详情
|
||||
export function getCategory(id) {
|
||||
return request({
|
||||
url: `/admin/categories/${id}`,
|
||||
url: `/backend/categories/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
@ -137,7 +137,7 @@ export function getCategory(id) {
|
||||
// 创建分类
|
||||
export function createCategory(data) {
|
||||
return request({
|
||||
url: `/admin/createCategory`,
|
||||
url: `/backend/createCategory`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
@ -146,7 +146,7 @@ export function createCategory(data) {
|
||||
// 更新分类
|
||||
export function editCategory(id, data) {
|
||||
return request({
|
||||
url: `/admin/editCategory/${id}`,
|
||||
url: `/backend/editCategory/${id}`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
@ -155,7 +155,7 @@ export function editCategory(id, data) {
|
||||
// 删除分类
|
||||
export function deleteCategory(id) {
|
||||
return request({
|
||||
url: `/admin/categories/${id}`,
|
||||
url: `/backend/categories/${id}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
@ -163,7 +163,7 @@ export function deleteCategory(id) {
|
||||
// 更新分类状态
|
||||
export function updateCategoryStatus(id, status) {
|
||||
return request({
|
||||
url: `/admin/categories/${id}/status`,
|
||||
url: `/backend/categories/${id}/status`,
|
||||
method: "patch",
|
||||
data: { status },
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import request from '@/utils/request'
|
||||
// 获取域名池列表
|
||||
export function getDomainPoolList(params) {
|
||||
return request({
|
||||
url: '/admin/domain/pool/index',
|
||||
url: '/backend/domain/pool/index',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
@ -14,7 +14,7 @@ export function getDomainPoolList(params) {
|
||||
// 获取启用的主域名列表
|
||||
export function getEnabledDomains() {
|
||||
return request({
|
||||
url: '/admin/domain/pool/getEnabledDomains',
|
||||
url: '/backend/domain/pool/getEnabledDomains',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@ -22,7 +22,7 @@ export function getEnabledDomains() {
|
||||
// 创建主域名
|
||||
export function createDomainPool(data) {
|
||||
return request({
|
||||
url: '/admin/domain/pool/create',
|
||||
url: '/backend/domain/pool/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
@ -31,7 +31,7 @@ export function createDomainPool(data) {
|
||||
// 更新主域名
|
||||
export function updateDomainPool(data) {
|
||||
return request({
|
||||
url: '/admin/domain/pool/update',
|
||||
url: '/backend/domain/pool/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
@ -40,7 +40,7 @@ export function updateDomainPool(data) {
|
||||
// 删除主域名
|
||||
export function deleteDomainPool(id) {
|
||||
return request({
|
||||
url: `/admin/domain/pool/delete/${id}`,
|
||||
url: `/backend/domain/pool/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -48,7 +48,7 @@ export function deleteDomainPool(id) {
|
||||
// 切换主域名状态
|
||||
export function toggleDomainPoolStatus(id) {
|
||||
return request({
|
||||
url: '/admin/domain/pool/toggleStatus',
|
||||
url: '/backend/domain/pool/toggleStatus',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
})
|
||||
@ -59,7 +59,7 @@ export function toggleDomainPoolStatus(id) {
|
||||
// 获取租户域名列表(管理员)
|
||||
export function getTenantDomainList(params) {
|
||||
return request({
|
||||
url: '/admin/domain/tenant/index',
|
||||
url: '/backend/domain/tenant/index',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
@ -68,7 +68,7 @@ export function getTenantDomainList(params) {
|
||||
// 获取当前租户的域名列表
|
||||
export function getMyDomains(params) {
|
||||
return request({
|
||||
url: '/admin/domain/tenant/myDomains',
|
||||
url: '/backend/domain/tenant/myDomains',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
@ -77,7 +77,7 @@ export function getMyDomains(params) {
|
||||
// 申请二级域名
|
||||
export function applyTenantDomain(data) {
|
||||
return request({
|
||||
url: '/admin/domain/tenant/apply',
|
||||
url: '/backend/domain/tenant/apply',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
@ -86,7 +86,7 @@ export function applyTenantDomain(data) {
|
||||
// 审核租户域名
|
||||
export function auditTenantDomain(data) {
|
||||
return request({
|
||||
url: '/admin/domain/tenant/audit',
|
||||
url: '/backend/domain/tenant/audit',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
@ -95,7 +95,7 @@ export function auditTenantDomain(data) {
|
||||
// 禁用/启用租户域名
|
||||
export function toggleTenantDomainStatus(id) {
|
||||
return request({
|
||||
url: '/admin/domain/tenant/toggleStatus',
|
||||
url: '/backend/domain/tenant/toggleStatus',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
})
|
||||
@ -104,7 +104,7 @@ export function toggleTenantDomainStatus(id) {
|
||||
// 删除租户域名
|
||||
export function deleteTenantDomain(id) {
|
||||
return request({
|
||||
url: `/admin/domain/tenant/delete/${id}`,
|
||||
url: `/backend/domain/tenant/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export function getTenantUsers(tenantId) {
|
||||
// 获取用户信息
|
||||
export function getUserInfo(userId) {
|
||||
return request({
|
||||
url: `/admin/getUserInfo/${userId}`,
|
||||
url: `/backend/getUserInfo/${userId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
244
src/views/basicSettings/tenants/components/TenantUsersTab.vue
Normal file
244
src/views/basicSettings/tenants/components/TenantUsersTab.vue
Normal file
@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="tenant-users-tab">
|
||||
<div class="section-header">
|
||||
<div class="section-title">用户列表</div>
|
||||
<el-button type="primary" size="small" :disabled="!tid" @click="handleAddUser">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加用户
|
||||
</el-button>
|
||||
</div>
|
||||
<el-form :inline="true" class="user-search-form" @submit.prevent>
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="userSearchKeyword"
|
||||
clearable
|
||||
placeholder="姓名 / 手机 / 邮箱 / 账号"
|
||||
style="width: 260px"
|
||||
:disabled="!tid"
|
||||
@keyup.enter="handleSearchUsers"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :disabled="!tid" @click="handleSearchUsers">查询</el-button>
|
||||
<el-button :disabled="!tid" @click="resetUserSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="tenantUsers" v-loading="usersLoading" border style="width: 100%">
|
||||
<el-table-column prop="account" label="用户名" min-width="140" align="center" />
|
||||
<el-table-column prop="name" label="姓名" min-width="120" align="center" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="140" align="center" />
|
||||
<el-table-column prop="email" label="邮箱" min-width="180" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.status) === 1 ? 'success' : 'danger'">
|
||||
{{ Number(row.status) === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" @click="openPasswordDialog(row)">
|
||||
修改密码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<AddUser ref="addUserRef" @success="refreshTenantUsers" />
|
||||
|
||||
<el-dialog v-model="passwordDialogVisible" title="修改密码" width="420px" destroy-on-close>
|
||||
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="90px">
|
||||
<el-form-item label="新密码" prop="password">
|
||||
<el-input v-model="passwordForm.password" type="password" show-password placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="password2">
|
||||
<el-input v-model="passwordForm.password2" type="password" show-password placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="passwordDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="passwordSubmitting" @click="submitPassword">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { getTenantUserList, editTenantUser } from "@/api/tenantUser";
|
||||
import AddUser from "./adduser.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
/** 当前租户 ID,为空时不请求 */
|
||||
tid: number | null;
|
||||
}>();
|
||||
|
||||
const tenantUsers = ref<any[]>([]);
|
||||
const usersLoading = ref(false);
|
||||
const userSearchKeyword = ref("");
|
||||
const addUserRef = ref<{ open: (tenantId: number) => void } | null>(null);
|
||||
|
||||
const passwordDialogVisible = ref(false);
|
||||
const passwordSubmitting = ref(false);
|
||||
const currentTenantUserId = ref<number | null>(null);
|
||||
const passwordFormRef = ref();
|
||||
const passwordForm = reactive({
|
||||
password: "",
|
||||
password2: "",
|
||||
});
|
||||
|
||||
const passwordRules = {
|
||||
password: [
|
||||
{ required: true, message: "请输入新密码", trigger: "blur" },
|
||||
{ min: 5, message: "密码至少 5 位", trigger: "blur" },
|
||||
],
|
||||
password2: [
|
||||
{ required: true, message: "请再次输入新密码", trigger: "blur" },
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
if (!value) return callback(new Error("请再次输入新密码"));
|
||||
if (value !== passwordForm.password) return callback(new Error("两次密码不一致"));
|
||||
callback();
|
||||
},
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const buildTenantUserQuery = (tid: number) => {
|
||||
const params: Record<string, string | number> = { tid };
|
||||
const kw = userSearchKeyword.value.trim();
|
||||
if (kw) params.keyword = kw;
|
||||
return params;
|
||||
};
|
||||
|
||||
const refreshTenantUsers = async () => {
|
||||
const id = props.tid;
|
||||
if (id == null) return;
|
||||
usersLoading.value = true;
|
||||
try {
|
||||
const usersRes = await getTenantUserList(buildTenantUserQuery(id));
|
||||
if (usersRes?.code === 200) {
|
||||
tenantUsers.value = usersRes?.data?.list || usersRes?.data || [];
|
||||
} else {
|
||||
tenantUsers.value = [];
|
||||
}
|
||||
} catch {
|
||||
tenantUsers.value = [];
|
||||
} finally {
|
||||
usersLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadUsersForTid = async (tid: number) => {
|
||||
usersLoading.value = true;
|
||||
try {
|
||||
const usersRes = await getTenantUserList({ tid });
|
||||
if (usersRes?.code === 200) {
|
||||
tenantUsers.value = usersRes?.data?.list || usersRes?.data || [];
|
||||
} else {
|
||||
tenantUsers.value = [];
|
||||
}
|
||||
} catch {
|
||||
tenantUsers.value = [];
|
||||
} finally {
|
||||
usersLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.tid,
|
||||
(id) => {
|
||||
userSearchKeyword.value = "";
|
||||
if (id == null) {
|
||||
tenantUsers.value = [];
|
||||
return;
|
||||
}
|
||||
loadUsersForTid(id);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const handleSearchUsers = () => {
|
||||
refreshTenantUsers();
|
||||
};
|
||||
|
||||
const resetUserSearch = () => {
|
||||
userSearchKeyword.value = "";
|
||||
refreshTenantUsers();
|
||||
};
|
||||
|
||||
const handleAddUser = () => {
|
||||
if (props.tid != null) {
|
||||
addUserRef.value?.open(props.tid);
|
||||
}
|
||||
};
|
||||
|
||||
const openPasswordDialog = (row: any) => {
|
||||
currentTenantUserId.value = Number(row?.id || 0) || null;
|
||||
passwordForm.password = "";
|
||||
passwordForm.password2 = "";
|
||||
passwordDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const submitPassword = async () => {
|
||||
if (!passwordFormRef.value || !currentTenantUserId.value) return;
|
||||
|
||||
try {
|
||||
await passwordFormRef.value.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
passwordSubmitting.value = true;
|
||||
try {
|
||||
const res = await editTenantUser(currentTenantUserId.value, { password: passwordForm.password });
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("密码修改成功");
|
||||
passwordDialogVisible.value = false;
|
||||
} else {
|
||||
ElMessage.error(res.msg || "密码修改失败");
|
||||
}
|
||||
} finally {
|
||||
passwordSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
refreshTenantUsers,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-users-tab {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.user-search-form {
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-search-form :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
144
src/views/basicSettings/tenants/components/adduser.vue
Normal file
144
src/views/basicSettings/tenants/components/adduser.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="添加用户" width="600px" @closed="handleClosed"
|
||||
destroy-on-close>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px" v-loading="loading"
|
||||
style="padding: 20px">
|
||||
<el-form-item label="用户名" prop="account">
|
||||
<el-input v-model="formData.account" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="password2">
|
||||
<el-input v-model="formData.password2" type="password" placeholder="请再次输入密码" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 租户详情内「添加用户」:提交到 /platform/tenantUser/create,
|
||||
* 后端写入 yz_tenant_user(租户-用户绑定,含冗余账号/密码等字段)。
|
||||
*/
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { createTenantUser } from '@/api/tenantUser';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const formRef = ref();
|
||||
const currentTenantId = ref<number | null>(null);
|
||||
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
status: 1
|
||||
});
|
||||
|
||||
const rules = {
|
||||
account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur', min: 5 }],
|
||||
password2: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请再次输入密码'));
|
||||
return;
|
||||
}
|
||||
if (value !== formData.password) {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
const open = (tenantId: number) => {
|
||||
visible.value = true;
|
||||
currentTenantId.value = tenantId;
|
||||
Object.assign(formData, {
|
||||
account: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
status: 1
|
||||
});
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch {
|
||||
// 校验失败时 validate 会 reject,避免 Uncaught (in promise)
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
try {
|
||||
const submitData = {
|
||||
...formData,
|
||||
tid: currentTenantId.value,
|
||||
};
|
||||
// 提交时不需要把确认密码发给后端
|
||||
delete (submitData as any).password2;
|
||||
const res = await createTenantUser(submitData);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功');
|
||||
visible.value = false;
|
||||
emit('success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败', error);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClosed = () => {
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
160
src/views/basicSettings/tenants/components/detail.vue
Normal file
160
src/views/basicSettings/tenants/components/detail.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="visible"
|
||||
title="租户详细信息"
|
||||
size="1200px"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div v-loading="loading" class="detail-container">
|
||||
<el-descriptions
|
||||
:column="2"
|
||||
:label-width="110"
|
||||
border
|
||||
class="tenant-detail-descriptions"
|
||||
>
|
||||
<el-descriptions-item label="租户名称">
|
||||
<span class="detail-value">{{ detailData.tenant_name }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="租户编码">
|
||||
<el-tag type="info" effect="plain">{{ detailData.tenant_code }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">
|
||||
{{ detailData.contact_person || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
{{ detailData.contact_phone || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="电子邮箱">
|
||||
{{ detailData.contact_email || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="租户地址" :span="2">
|
||||
{{ detailData.address || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="租户状态">
|
||||
<el-tag :type="detailData.status === 1 ? 'success' : 'danger'">
|
||||
{{ detailData.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ detailData.create_time || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="tenant-detail-body">
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
tab-position="left"
|
||||
class="tenant-detail-tabs"
|
||||
>
|
||||
<el-tab-pane label="租户用户" name="users">
|
||||
<div class="tab-pane-inner">
|
||||
<TenantUsersTab :tid="detailTenantId" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- 后续功能:在此继续增加 <el-tab-pane label="..." name="...">...</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- <div class="footer-actions">
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
</div> -->
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { getTenantDetail } from "@/api/tenant";
|
||||
import TenantUsersTab from "./TenantUsersTab.vue";
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const detailData = ref<any>({});
|
||||
const detailTenantId = ref<number | null>(null);
|
||||
const activeTab = ref("users");
|
||||
|
||||
const open = async (id: number) => {
|
||||
detailTenantId.value = id;
|
||||
activeTab.value = "users";
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
const detailRes = await getTenantDetail(id);
|
||||
if (detailRes.code === 200) {
|
||||
detailData.value = detailRes.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取详情失败", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClosed = () => {
|
||||
detailData.value = {};
|
||||
detailTenantId.value = null;
|
||||
activeTab.value = "users";
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-container {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.detail-value {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.footer-actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tenant-detail-body {
|
||||
margin-top: 20px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tenant-detail-tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tenant-detail-tabs :deep(.el-tabs__header) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tenant-detail-tabs :deep(.el-tabs__nav-wrap.is-left) {
|
||||
min-width: 112px;
|
||||
}
|
||||
|
||||
.tenant-detail-tabs :deep(.el-tabs__item) {
|
||||
justify-content: flex-start;
|
||||
padding: 0 16px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.tenant-detail-tabs :deep(.el-tabs__content) {
|
||||
padding: 0 0 0 16px;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.tab-pane-inner {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.tenant-detail-descriptions :deep(.el-descriptions__table) {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
</style>
|
||||
194
src/views/basicSettings/tenants/components/edit.vue
Normal file
194
src/views/basicSettings/tenants/components/edit.vue
Normal file
@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="formData.id ? '编辑租户' : '添加租户'" width="600px" @closed="handleClosed"
|
||||
destroy-on-close>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px" v-loading="loading"
|
||||
style="padding: 20px">
|
||||
<el-form-item label="租户编码" prop="tenant_code">
|
||||
<el-input v-model="formData.tenant_code" placeholder="系统自动生成" disabled />
|
||||
<div class="form-tip" v-if="!formData.id" style="font-size: 12px; color: #999;">
|
||||
* 编码由系统随机分配,提交时将自动校验唯一性
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户名称" prop="tenant_name">
|
||||
<el-input v-model="formData.tenant_name" placeholder="请输入租户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contact_person">
|
||||
<el-input v-model="formData.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contact_phone">
|
||||
<el-input v-model="formData.contact_phone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮箱" prop="contact_email">
|
||||
<el-input v-model="formData.contact_email" placeholder="请输入电子邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="租户地址" prop="address">
|
||||
<el-input v-model="formData.address" type="textarea" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { createTenant, editTenant, getTenantDetail, checkTenantCode } from '@/api/tenant';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const formRef = ref();
|
||||
|
||||
const initialData = {
|
||||
id: null,
|
||||
tenant_name: '',
|
||||
tenant_code: '',
|
||||
contact_person: '',
|
||||
contact_phone: '',
|
||||
contact_email: '',
|
||||
address: '',
|
||||
status: 1
|
||||
};
|
||||
|
||||
const formData = reactive({ ...initialData });
|
||||
|
||||
const rules = {
|
||||
tenant_name: [{ required: true, message: '请输入租户名称', trigger: 'blur' }],
|
||||
tenant_code: [{ required: true, message: '请输入租户编码', trigger: 'blur' }],
|
||||
contact_phone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
const open = (id?: number) => {
|
||||
visible.value = true;
|
||||
Object.assign(formData, initialData);
|
||||
|
||||
if (id) {
|
||||
formData.id = id;
|
||||
fetchDetail(id);
|
||||
} else {
|
||||
formData.tenant_code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDetail = async (id: number) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getTenantDetail(id);
|
||||
if (res.code === 200) {
|
||||
Object.assign(formData, res.data);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
// 1. 基础表单验证(必填项等)
|
||||
await formRef.value.validate();
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
try {
|
||||
// 2. 如果是新增模式,进入“编码唯一性”校验环
|
||||
if (!formData.id) {
|
||||
let isCodeValid = false;
|
||||
|
||||
while (!isCodeValid) {
|
||||
const res = await checkTenantCode(formData.tenant_code);
|
||||
|
||||
if (res.code === 200) {
|
||||
// 编码可用,跳出循环
|
||||
isCodeValid = true;
|
||||
} else {
|
||||
// 编码重复,重新生成
|
||||
const newCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
|
||||
// 弹出提示框告知用户
|
||||
await ElMessageBox.alert(
|
||||
`租户编码 [${formData.tenant_code}] 已重复,系统已自动为您重新生成为 [${newCode}],请重新点击提交。`,
|
||||
'编码重复提示',
|
||||
{ confirmButtonText: '我知道了', type: 'warning' }
|
||||
);
|
||||
|
||||
formData.tenant_code = newCode;
|
||||
submitting.value = false;
|
||||
return; // 中断本次提交,让用户看一眼新编码后再次点击
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 发起真正的保存请求
|
||||
const saveApi = formData.id ? editTenant(formData.id, formData) : createTenant(formData);
|
||||
const saveRes = await saveApi;
|
||||
|
||||
if (saveRes.code === 200) {
|
||||
ElMessage.success('保存成功');
|
||||
visible.value = false;
|
||||
emit('success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败', error);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成 6 位随机数字并校验唯一性
|
||||
*/
|
||||
const generateUniqueCode = async () => {
|
||||
loading.value = true;
|
||||
let isUnique = false;
|
||||
let newCode = '';
|
||||
let retryCount = 0;
|
||||
const maxRetries = 10; // 保护措施:最多重试10次
|
||||
|
||||
while (!isUnique && retryCount < maxRetries) {
|
||||
// 1. 生成 6 位随机数字字符串
|
||||
newCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
|
||||
try {
|
||||
// 2. 调用接口校验
|
||||
const res = await checkTenantCode(newCode);
|
||||
if (res.code === 200) {
|
||||
isUnique = true; // 接口返回 200 表示不存在,可用
|
||||
} else {
|
||||
console.warn(`编码 ${newCode} 重复,正在重试...`);
|
||||
retryCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("校验编码失败", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnique) {
|
||||
formData.tenant_code = newCode;
|
||||
} else {
|
||||
ElMessage.error('无法生成唯一的租户编码,请重试');
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handleClosed = () => {
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
166
src/views/basicSettings/tenants/components/qualification.vue
Normal file
166
src/views/basicSettings/tenants/components/qualification.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="资质文件管理" width="650px" destroy-on-close>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="110px"
|
||||
v-loading="loading"
|
||||
style="padding: 10px 20px"
|
||||
>
|
||||
<el-form-item label="租户名称">
|
||||
<el-input v-model="tenantName" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="资质类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择资质类型" style="width: 100%">
|
||||
<el-option label="营业执照" value="business_license" />
|
||||
<el-option label="开户许可证" value="bank_account_permit" />
|
||||
<el-option label="行业许可证" value="industry_license" />
|
||||
<el-option label="其他资质" value="others" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="资质图片" prop="file_url">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
action="/api/platform/common/upload"
|
||||
:show-file-list="false"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
name="file"
|
||||
>
|
||||
<img v-if="formData.file_url" :src="formData.file_url" class="qualification-img" />
|
||||
<el-icon v-else class="uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<div class="upload-tip">支持 jpg/png 格式,大小不超过 2MB</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="有效期至" prop="expire_time">
|
||||
<el-date-picker
|
||||
v-model="formData.expire_time"
|
||||
type="date"
|
||||
placeholder="选择过期日期"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注说明">
|
||||
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm">保存资质</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
// 假设你有对应的资质接口
|
||||
// import { saveQualification, getQualificationDetail } from '@/api/tenant';
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const tenantName = ref('');
|
||||
const formRef = ref();
|
||||
|
||||
const formData = reactive({
|
||||
tid: null,
|
||||
type: '',
|
||||
file_url: '',
|
||||
expire_time: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
const rules = {
|
||||
type: [{ required: true, message: '请选择资质类型', trigger: 'change' }],
|
||||
file_url: [{ required: true, message: '请上传资质图片', trigger: 'change' }],
|
||||
expire_time: [{ required: true, message: '请选择有效期', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const open = (row: any) => {
|
||||
visible.value = true;
|
||||
tenantName.value = row.tenant_name;
|
||||
formData.tid = row.id;
|
||||
|
||||
// 如果后端有资质详情接口,可以在此获取回显数据
|
||||
// fetchDetail(row.id);
|
||||
};
|
||||
|
||||
// 上传前的校验
|
||||
const beforeAvatarUpload = (rawFile: any) => {
|
||||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||||
ElMessage.error('图片必须是 JPG 或 PNG 格式!');
|
||||
return false;
|
||||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
ElMessage.error('图片大小不能超过 2MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 上传成功回调
|
||||
const handleUploadSuccess = (response: any) => {
|
||||
// 根据你后端的上传接口返回结构调整
|
||||
formData.file_url = response.data.url;
|
||||
ElMessage.success('上传成功');
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate();
|
||||
submitting.value = true;
|
||||
try {
|
||||
// 模拟提交
|
||||
console.log('提交的数据:', formData);
|
||||
ElMessage.success('资质保存成功');
|
||||
visible.value = false;
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.avatar-uploader {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
.avatar-uploader:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
line-height: 178px !important;
|
||||
}
|
||||
.qualification-img {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
.upload-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
258
src/views/basicSettings/tenants/domain.vue
Normal file
258
src/views/basicSettings/tenants/domain.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>我的域名</h2>
|
||||
<el-button type="primary" @click="dialogVisible = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
申请二级域名
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- 我的域名列表 -->
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
border
|
||||
v-loading="loading"
|
||||
element-loading-text="正在加载..."
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="sub_domain" label="二级域名前缀" width="150" />
|
||||
<el-table-column prop="main_domain" label="主域名" min-width="150" />
|
||||
<el-table-column prop="full_domain" label="完整域名" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
type="primary"
|
||||
:href="'http://' + scope.row.full_domain"
|
||||
target="_blank"
|
||||
>
|
||||
{{ scope.row.full_domain }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 0" type="warning">审核中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 1" type="success"
|
||||
>已生效</el-tag
|
||||
>
|
||||
<el-tag v-else type="danger">已禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="180" />
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleCopy(scope.row.full_domain)"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 申请域名弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" title="申请二级域名" width="500px">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||
<el-form-item label="选择主域名" prop="main_domain">
|
||||
<el-select
|
||||
v-model="form.main_domain"
|
||||
placeholder="请选择主域名"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in domainList"
|
||||
:key="item.main_domain"
|
||||
:label="item.main_domain"
|
||||
:value="item.main_domain"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="二级前缀" prop="sub_domain">
|
||||
<el-input v-model="form.sub_domain" placeholder="请输入二级域名前缀">
|
||||
<template #append>{{
|
||||
form.main_domain ? "." + form.main_domain : ""
|
||||
}}</template>
|
||||
</el-input>
|
||||
<div class="form-tip">
|
||||
只能包含字母、数字和连字符,不能以连字符开头或结尾
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预览">
|
||||
<div class="domain-preview">
|
||||
{{
|
||||
form.sub_domain
|
||||
? form.sub_domain + "." + (form.main_domain || "example.com")
|
||||
: "请填写上方信息"
|
||||
}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading"
|
||||
>提交申请</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getMyDomains,
|
||||
applyTenantDomain,
|
||||
getEnabledDomains,
|
||||
deleteTenantDomain,
|
||||
} from "@/api/domain";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const dialogVisible = ref(false);
|
||||
const formRef = ref();
|
||||
|
||||
const tableData = ref<any[]>([]);
|
||||
const domainList = ref<any[]>([]);
|
||||
|
||||
const form = reactive({
|
||||
main_domain: "",
|
||||
sub_domain: "",
|
||||
});
|
||||
|
||||
const rules = {
|
||||
main_domain: [{ required: true, message: "请选择主域名", trigger: "change" }],
|
||||
sub_domain: [
|
||||
{ required: true, message: "请输入二级域名前缀", trigger: "blur" },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/,
|
||||
message: "格式不正确",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const fetchDomains = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 获取我的域名列表
|
||||
const res = await getMyDomains({ tid: authStore.user.tid });
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.data || [];
|
||||
}
|
||||
|
||||
// 获取可选主域名
|
||||
const domainRes = await getEnabledDomains();
|
||||
if (domainRes.code === 200) {
|
||||
domainList.value = domainRes.data || [];
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value.validate();
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
const res = await applyTenantDomain({
|
||||
tid: authStore.user.tid,
|
||||
main_domain: form.main_domain,
|
||||
sub_domain: form.sub_domain,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg);
|
||||
dialogVisible.value = false;
|
||||
form.sub_domain = "";
|
||||
fetchDomains();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "申请失败");
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = (domain: string) => {
|
||||
navigator.clipboard.writeText("http://" + domain);
|
||||
ElMessage.success("链接已复制到剪贴板");
|
||||
};
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除域名 "${row.full_domain}" 吗?`,
|
||||
"提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
}
|
||||
);
|
||||
const res = await deleteTenantDomain(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
fetchDomains();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error !== "cancel") {
|
||||
console.error("删除失败:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchDomains();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container-box {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.domain-preview {
|
||||
padding: 10px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
</style>
|
||||
299
src/views/basicSettings/tenants/index.vue
Normal file
299
src/views/basicSettings/tenants/index.vue
Normal file
@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>租户管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="editRef.open()">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
添加租户
|
||||
</el-button>
|
||||
<el-button @click="refresh">
|
||||
<el-icon>
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="租户名称">
|
||||
<el-input
|
||||
v-model="searchForm.tenant_name"
|
||||
placeholder="请输入租户名称"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户编码">
|
||||
<el-input
|
||||
v-model="searchForm.tenant_code"
|
||||
placeholder="请输入租户编码"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input
|
||||
v-model="searchForm.contact_person"
|
||||
placeholder="请输入联系人"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input
|
||||
v-model="searchForm.contact_phone"
|
||||
placeholder="请输入联系电话"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 租户列表表格 -->
|
||||
<el-table :data="tenants" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" align="center" fixed="left" />
|
||||
<el-table-column
|
||||
prop="tenant_name"
|
||||
label="租户名称"
|
||||
min-width="220"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span class="name-link" @click="handlePreview(scope.row)">
|
||||
{{ scope.row.tenant_name }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tenant_code"
|
||||
label="租户编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contact_person"
|
||||
label="联系人"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contact_phone"
|
||||
label="联系电话"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contact_email"
|
||||
label="电子邮箱"
|
||||
min-width="180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="address"
|
||||
label="租户地址"
|
||||
min-width="300"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="status" label="租户状态" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{
|
||||
scope.row.status === 1 ? "启用" : "禁用"
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="action-icons">
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button text size="small" @click="editRef.open(scope.row.id)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
text
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<EditModal ref="editRef" @success="refresh" />
|
||||
<DetailDrawer ref="detailRef" />
|
||||
<Qualification ref="qualificationRef" />
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-bar">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
@current-change="handlePageChange"
|
||||
layout="total, prev, pager, next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getTenantList, deleteTenant } from "@/api/tenant";
|
||||
import { onMounted, ref, reactive } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import EditModal from "./components/edit.vue";
|
||||
import DetailDrawer from "./components/detail.vue";
|
||||
import Qualification from "./components/qualification.vue";
|
||||
import { Edit, Delete } from "@element-plus/icons-vue";
|
||||
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const loading = ref(false);
|
||||
const router = useRouter();
|
||||
const tenants = ref([]);
|
||||
|
||||
const editRef = ref();
|
||||
const detailRef = ref();
|
||||
const qualificationRef = ref();
|
||||
|
||||
// 删除
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm("确定要删除该租户吗?", "提示", {
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
deleteTenant(row.id).then((res) => {
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 查看
|
||||
const handlePreview = (row: any) => {
|
||||
detailRef.value.open(row.id);
|
||||
};
|
||||
|
||||
// 资质
|
||||
const handleQualification = (row: any) => {
|
||||
qualificationRef.value.open(row.id);
|
||||
};
|
||||
|
||||
// 分页改变
|
||||
const handlePageChange = (val: number) => {
|
||||
page.value = val;
|
||||
refresh();
|
||||
};
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive({
|
||||
tenant_name: "",
|
||||
tenant_code: "",
|
||||
contact_person: "",
|
||||
contact_phone: "",
|
||||
});
|
||||
|
||||
// 执行查询(回到第一页再刷新)
|
||||
const handleSearch = () => {
|
||||
page.value = 1;
|
||||
refresh();
|
||||
};
|
||||
|
||||
// 重置查询
|
||||
const resetSearch = () => {
|
||||
searchForm.tenant_name = "";
|
||||
searchForm.tenant_code = "";
|
||||
searchForm.contact_person = "";
|
||||
searchForm.contact_phone = "";
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 刷新
|
||||
const refresh = () => {
|
||||
loading.value = true;
|
||||
|
||||
// 整合分页参数和搜索参数
|
||||
const queryParams = {
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchForm,
|
||||
};
|
||||
|
||||
getTenantList(queryParams)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
tenants.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取租户列表
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-alert__title) {
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
|
||||
.name-link {
|
||||
color: #3973ff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background: #f9f9f9;
|
||||
padding: 20px 20px 0 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-icons {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user