diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts
index 9ad73ef..8422cea 100644
--- a/src/locales/langs/en-us.ts
+++ b/src/locales/langs/en-us.ts
@@ -24,6 +24,7 @@ const local: any = {
columnSetting: 'Column Setting',
config: 'Config',
confirm: 'Confirm',
+ submit:'Confirm',
delete: 'Delete',
view: 'View',
exportOk: "Export Completed",
@@ -52,6 +53,8 @@ const local: any = {
update: 'Update',
updateSuccess: 'Update Success',
userCenter: 'User Center',
+ setemail:'Bind Email',
+ resetpwd:'Reset Password',
yesOrNo: {
yes: 'Yes',
no: 'No'
@@ -670,6 +673,24 @@ const local: any = {
clients:'CLIENTS',
search:'Search site name',
total:'Total',
+ addsite:'Add New Site',
+ region:'Country/Region',
+ timezone:'TimeZone',
+ scenario:'Scenario',
+ username:'Username',
+ password:'Password',
+ deleteConfirmTitle:'Are you sure to delete',
+ deleteConfirmContent:'Are you sure to delete {name}?',
+ confirm:'Confirm',
+ cancel:'Cancel',
+ deleteSuccess:'Delete Success',
+ editsite:'Edit Site',
+ office:'Office',
+ hotel:'Hotel',
+ education:'Education',
+ retail:'Retail',
+ other:'Other',
+ updateSuccess:'Update Success',
},
headerbanner:{
controller:'Controller Overview',
@@ -1105,7 +1126,42 @@ const local: any = {
confirm:'Determine',
close:'Cancel',
nozero:'The sort must be greater than or equal to 0'
- }
+ },
+ resetPwd:{
+ title:'Change your password',
+ byEmail:'Change by email',
+ byPassword:'Change by old password',
+ email:'Email',
+ getCode:'Get code',
+ code:'Code',
+ oldPassword:'Old password',
+ newPassword:'New password',
+ confirmPassword:'confirm password',
+ emailPlaceholder:'Please enter the email',
+ codePlaceholder:'Please enter the code',
+ passwordPlaceholder:'Please enter new password',
+ oldPasswordPlaceholder:'Please enter old password',
+ confirmPasswordPlaceholder:'Please enter again',
+ submit:'Confirm',
+ emailRequired:'Email can not be empty',
+ codeSent:'Code enter',
+ success:'Reset Success',
+ },
+ email:{
+ title:'Bind Email',
+ currentEmail:'Current Email',
+ email:'Email',
+ code:'Code',
+ getCode:'Get Code',
+ submit:'Submit',
+ codeSent:'Code Sent',
+ codeRequired:'Code can not be empty',
+ updateSuccess:'Update Success',
+ updateFailed:'Update Failed',
+ emailRequired:'Email can not be empty',
+ emailInvalid:'Email Invalid',
+ codeLength:'Code Invalid',
+ },
},
form: {
required: 'Cannot be empty',
diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts
index 4f2b2ca..b6fc5b8 100644
--- a/src/locales/langs/zh-cn.ts
+++ b/src/locales/langs/zh-cn.ts
@@ -24,6 +24,7 @@ const local:any = {
columnSetting: '列设置',
config: '配置',
confirm: '确认',
+ submit:'确定',
delete: '删除',
view:'查看',
exportOk: "已完成导出",
@@ -52,6 +53,8 @@ const local:any = {
update: '更新',
updateSuccess: '更新成功',
userCenter: '个人中心',
+ setemail:'绑定邮箱',
+ resetpwd:'修改密码',
yesOrNo: {
yes: '是',
no: '否'
@@ -670,6 +673,24 @@ const local:any = {
clients:'装置',
search:'输入站点名称',
total:'共',
+ addsite:'添加站点',
+ region:'国家/地区',
+ timezone:'时区',
+ scenario:'场景',
+ username:'用户名',
+ password:'密码',
+ deleteConfirmTitle:'确定要删除吗',
+ deleteConfirmContent:'确定要删除站点{name}吗?',
+ confirm:'确定',
+ cancel:'取消',
+ deleteSuccess:'删除成功',
+ editsite:'修改配置',
+ office:'办公室',
+ hotel:'医院',
+ education:'教育',
+ retail:'零售业',
+ other:'其他',
+ updateSuccess:'更新成功',
},
headerbanner:{
controller:'控制仪表盘',
@@ -1106,7 +1127,42 @@ const local:any = {
confirm:'确定',
close:'取消',
nozero:'排序必须大于等于0'
- }
+ },
+ resetPwd:{
+ title:'修改密码',
+ byEmail:'通过邮箱修改密码',
+ byPassword:'通过原密码修改密码',
+ email:'邮箱',
+ getCode:'获取验证码',
+ code:'验证码',
+ oldPassword:'原密码',
+ newPassword:'新密码',
+ confirmPassword:'确认密码',
+ emailPlaceholder:'请输入邮箱',
+ codePlaceholder:'请输入验证码',
+ passwordPlaceholder:'请输入新密码',
+ oldPasswordPlaceholder:'请输入旧密码',
+ confirmPasswordPlaceholder:'请再次输入新密码',
+ submit:'确认',
+ emailRequired:'邮箱不能为空',
+ codeSent:'验证码发送',
+ success:'修改成功',
+ },
+ email:{
+ title:'绑定邮箱',
+ currentEmail:'当前邮箱',
+ email:'新邮箱',
+ code:'验证码',
+ getCode:'获取验证码',
+ submit:'确定',
+ codeSent:'验证码发送',
+ codeRequired:'验证码不能为空',
+ updateSuccess:'修改成功',
+ updateFailed:'修改失败',
+ emailRequired:'邮箱不能为空',
+ emailInvalid:'邮箱格式错误',
+ codeLength:'验证码长度错误',
+ },
},
form: {
required: '不能为空',
diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts
index 49875fb..6f0d4f2 100644
--- a/src/router/elegant/transform.ts
+++ b/src/router/elegant/transform.ts
@@ -200,7 +200,9 @@ const routeMap: RouteMap = {
'function_toggle-auth': '/function/toggle-auth',
'dashboard': '/dashboard',
'login': '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
- 'user-center': '/user-center'
+ 'user-center': '/user-center',
+ 'reset-pwd':'/reset-pwd',
+ 'forgot-pwd':'/user-center/resetpwd'
};
/**
diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts
index abaf9ca..b025ce3 100644
--- a/src/service/api/auth.ts
+++ b/src/service/api/auth.ts
@@ -407,12 +407,6 @@ export function deletePortal(siteId: string, portalId: string) {
});
}
-
-
-
-
-
-
/** 首页邮箱重置密码 */
export function fetcodeReset(data:any) {
return request({
@@ -421,6 +415,81 @@ export function fetcodeReset(data:any) {
data
});
}
+/** 获取邮箱验证码 */
+export function getEmailCode(email: string) {
+ return request
({
+ url: `/system/email/code`,
+ method: 'get',
+ params: { email }
+ });
+}
+
+/** 重置密码 */
+export function resetPassword(data: { email: string; code: string; password: string }) {
+ return request({
+ url: '/system/user/profile/forgotPwd',
+ method: 'put',
+ data
+ });
+}
+/** 通过原密码修改密码 */
+export function updatePasswordByOld(data: { oldPassword: string; newPassword: string }) {
+ return request({
+ url: '/system/user/profile/updatePwd',
+ method: 'put',
+ data
+ });
+}
+/** 添加站点 */
+export function addSite(data: Api.Site.AddSiteParams) {
+ return request({
+ url: '/system/site',
+ method: 'post',
+ data
+ });
+}
+
+/** 删除站点 */
+export function deleteSite(siteId: string) {
+ return request({
+ url: `/system/site/${siteId}`,
+ method: 'delete'
+ });
+}
+
+/** 获取站点配置 */
+export function getSiteConfig(siteId: string) {
+ return request({
+ url: `/system/site/${siteId}`,
+ method: 'get'
+ });
+}
+
+
+/** 更新站点配置 */
+export function updateSite(siteId: string, data: Api.Site.UpdateSiteParams) {
+ return request({
+ url: `/system/site/${siteId}`,
+ method: 'put',
+ data
+ });
+}
+/** 获取用户信息 */
+export function getUserProfile() {
+ return request({
+ url: '/system/user/profile',
+ method: 'get'
+ });
+}
+
+/** 更新用户信息 */
+export function updateUserProfile(data: { email: string; code: string }) {
+ return request({
+ url: '/system/user/profile',
+ method: 'put',
+ data
+ });
+}
diff --git a/src/typings/api.d.ts b/src/typings/api.d.ts
index b50bc19..65cc296 100644
--- a/src/typings/api.d.ts
+++ b/src/typings/api.d.ts
@@ -788,6 +788,30 @@ declare namespace Api {
supportL2: boolean;
}
+ interface AddSiteParams {
+ name: string;
+ region: string;
+ timeZone: string;
+ scenario: string;
+ deviceAccountSetting: {
+ username: string;
+ password: string;
+ };
+ }
+ interface SiteConfig {
+ name: string;
+ region: string;
+ timeZone: string;
+ scenario: string;
+ }
+
+ interface UpdateSiteParams {
+ name: string;
+ region: string;
+ timeZone: string;
+ scenario: string;
+ }
+
interface SiteParams {
pageNum: number;
pageSize: number;
diff --git a/src/typings/auto-imports.d.ts b/src/typings/auto-imports.d.ts
index 6642315..5dbf030 100644
--- a/src/typings/auto-imports.d.ts
+++ b/src/typings/auto-imports.d.ts
@@ -16,6 +16,7 @@ declare global {
const addPackage: typeof import('../service/api/auth')['addPackage']
const addPortal: typeof import('../service/api/auth')['addPortal']
const addRateLimit: typeof import('../service/api/auth')['addRateLimit']
+ const addSite: typeof import('../service/api/auth')['addSite']
const addThemeVarsToHtml: typeof import('../store/modules/theme/shared')['addThemeVarsToHtml']
const addWlanSsid: typeof import('../service/api/auth')['addWlanSsid']
const adoptApDevice: typeof import('../service/api/auth')['adoptApDevice']
@@ -68,6 +69,7 @@ declare global {
const deleteApDevices: typeof import('../service/api/auth')['deleteApDevices']
const deletePackage: typeof import('../service/api/auth')['deletePackage']
const deletePortal: typeof import('../service/api/auth')['deletePortal']
+ const deleteSite: typeof import('../service/api/auth')['deleteSite']
const deleteWlanSsid: typeof import('../service/api/auth')['deleteWlanSsid']
const describe: typeof import('vitest')['describe']
const dict: typeof import('../store/modules/dict/index')['default']
@@ -176,6 +178,7 @@ declare global {
const getDefaultHomeTab: typeof import('../store/modules/tab/shared')['getDefaultHomeTab']
const getDictDataType: typeof import('../service/api/dict')['getDictDataType']
const getDictOptionselect: typeof import('../service/api/dict')['getDictOptionselect']
+ const getEmailCode: typeof import('../service/api/auth')['getEmailCode']
const getFixedTabIds: typeof import('../store/modules/tab/shared')['getFixedTabIds']
const getFixedTabs: typeof import('../store/modules/tab/shared')['getFixedTabs']
const getGlobalMenusByAuthRoutes: typeof import('../store/modules/route/shared')['getGlobalMenusByAuthRoutes']
@@ -184,9 +187,11 @@ declare global {
const getRouteIcons: typeof import('../store/modules/tab/shared')['getRouteIcons']
const getSelectedMenuKeyPathByKey: typeof import('../store/modules/route/shared')['getSelectedMenuKeyPathByKey']
const getServiceBaseURL: typeof import('../utils/service')['getServiceBaseURL']
+ const getSiteConfig: typeof import('../service/api/auth')['getSiteConfig']
const getTabByRoute: typeof import('../store/modules/tab/shared')['getTabByRoute']
const getTabIdByRoute: typeof import('../store/modules/tab/shared')['getTabIdByRoute']
const getToken: typeof import('../store/modules/auth/shared')['getToken']
+ const getUserProfile: typeof import('../service/api/auth')['getUserProfile']
const getWlanSsidConfig: typeof import('../service/api/auth')['getWlanSsidConfig']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
@@ -255,6 +260,7 @@ declare global {
const rejectKyc: typeof import('../service/api/auth')['rejectKyc']
const removeEmptyChildren: typeof import('../utils/menu')['removeEmptyChildren']
const removeRateLimit: typeof import('../service/api/auth')['removeRateLimit']
+ const resetPassword: typeof import('../service/api/auth')['resetPassword']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
@@ -306,9 +312,12 @@ declare global {
const updateJob: typeof import('../service/api/job')['updateJob']
const updateLocaleOfGlobalMenus: typeof import('../store/modules/route/shared')['updateLocaleOfGlobalMenus']
const updatePackage: typeof import('../service/api/auth')['updatePackage']
+ const updatePasswordByOld: typeof import('../service/api/auth')['updatePasswordByOld']
const updatePortalConfig: typeof import('../service/api/auth')['updatePortalConfig']
+ const updateSite: typeof import('../service/api/auth')['updateSite']
const updateTabByI18nKey: typeof import('../store/modules/tab/shared')['updateTabByI18nKey']
const updateTabsByI18nKey: typeof import('../store/modules/tab/shared')['updateTabsByI18nKey']
+ const updateUserProfile: typeof import('../service/api/auth')['updateUserProfile']
const updateWlanSsid: typeof import('../service/api/auth')['updateWlanSsid']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
diff --git a/src/views/dashboard/modules/card-data.vue b/src/views/dashboard/modules/card-data.vue
index 071e1dd..0505e45 100644
--- a/src/views/dashboard/modules/card-data.vue
+++ b/src/views/dashboard/modules/card-data.vue
@@ -4,15 +4,107 @@ import type { TableColumnType } from 'ant-design-vue';
import {
EnvironmentOutlined,
SearchOutlined,
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined
} from '@ant-design/icons-vue';
-import { getDashboardSiteList } from '@/service/api/auth';
+import { getDashboardSiteList, addSite, deleteSite, getSiteConfig, updateSite } from '@/service/api/auth';
import { useI18n } from 'vue-i18n';
+import { Form, Modal } from 'ant-design-vue';
+import { message } from 'ant-design-vue';
+import { regionOptions, timeZoneOptions } from '@/constants/site-options';
const { t } = useI18n();
defineOptions({
name: 'CardData'
});
+// 表单实例
+const formRef = ref();
+const useForm = Form.useForm;
+
+// 弹窗控制
+const showAddDialog = ref(false);
+
+// 表单数据
+const formData = ref({
+ name: '',
+ region: '',
+ timeZone: '',
+ scenario: '',
+ deviceAccountSetting: {
+ username: '',
+ password: ''
+ }
+});
+
+// 表单验证规则
+const { validate, validateInfos } = useForm(formData, {
+ name: [
+ { required: true, message: t('page.carddata.nameRequired') },
+ {
+ pattern: /^[^ \+\-\@\=]$|^[^ \+\-\@\=].{0,62}[^ ]$/,
+ message: t('page.carddata.nameInvalid')
+ }
+ ],
+ region: [{ required: true, message: t('page.carddata.regionRequired') }],
+ timeZone: [{ required: true, message: t('page.carddata.timeZoneRequired') }],
+ scenario: [{ required: true, message: t('page.carddata.scenarioRequired') }],
+ 'deviceAccountSetting.username': [
+ { required: true, message: t('page.carddata.usernameRequired') },
+ {
+ pattern: /^[\x21-\x7E]{1,64}$/,
+ message: t('page.carddata.usernameInvalid')
+ }
+ ],
+ 'deviceAccountSetting.password': [
+ { required: true, message: t('page.carddata.passwordRequired') },
+ {
+ pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\!\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\@\[\\\]\^\_\`\{\|\}\~])(?!.*[\00-\040\042\077\0177]).{8,64}$/,
+ message: t('page.carddata.passwordInvalid')
+ }
+ ]
+});
+
+// 使用场景选项
+const scenarioOptions = [
+ { label: t('page.carddata.office'), value: 'office' },
+ { label: t('page.carddata.hotel'), value: 'hotel' },
+ { label: t('page.carddata.education'), value: 'education' },
+ { label: t('page.carddata.retail'), value: 'retail' },
+ { label: t('page.carddata.other'), value: 'other' }
+];
+
+// 处理添加站点
+const handleAddSite = async () => {
+ try {
+ await validate();
+ const response = await addSite(formData.value);
+ if (response) {
+ message.success(t('page.carddata.addSuccess'));
+ showAddDialog.value = false;
+ fetchSiteList(); // 刷新列表
+ }
+ } catch (error) {
+ console.error('Add site failed:', error);
+ message.error(t('page.carddata.addFailed'));
+ }
+};
+
+// 打开添加对话框
+const openAddDialog = () => {
+ formData.value = {
+ name: '',
+ region: '',
+ timeZone: '',
+ scenario: '',
+ deviceAccountSetting: {
+ username: '',
+ password: ''
+ }
+ };
+ showAddDialog.value = true;
+};
// 搜索和分页状态
const searchValue = ref('');
@@ -100,25 +192,37 @@ const columns: TableColumnType[] = [
key: 'clients',
width: 150
},
- // {
- // title: 'ACTION',
- // key: 'action',
- // width: 150,
- // fixed: 'right'
- // }
+ {
+ title: 'ACTION',
+ key: 'action',
+ width: 100,
+ fixed: 'right'
+ }
];
// 按钮操作处理函数
-// const handleEdit = (record: Api.DashboardSite) => {
-// console.log('Edit:', record);
-// };
+
//
// const handleCopy = (record: Api.DashboardSite) => {
// console.log('Copy:', record);
// };
//
-// const handleDelete = (record: Api.DashboardSite) => {
-// console.log('Delete:', record);
-// };
+const handleDelete = (record: Api.DashboardSite) => {
+ Modal.confirm({
+ title: t('page.carddata.deleteConfirmTitle'),
+ content: t('page.carddata.deleteConfirmContent', { name: record.name }),
+ okText: t('page.carddata.confirm'),
+ cancelText: t('page.carddata.cancel'),
+ okType: 'danger',
+ async onOk() {
+ try {
+ await deleteSite(record.siteId);
+ message.success(t('page.carddata.deleteSuccess'));
+ fetchSiteList(); // 刷新列表
+ } catch (error) {
+ }
+ }
+ });
+};
//
// const handleHome = (record: Api.DashboardSite) => {
// console.log('Home:', record);
@@ -136,6 +240,68 @@ const handlePageSizeChange = (size: number) => {
pageSize.value = size;
currentPage.value = 1;
};
+
+// 编辑弹窗控制
+const showEditDialog = ref(false);
+
+// 编辑表单数据
+const editFormData = ref({
+ name: '',
+ region: '',
+ timeZone: '',
+ scenario: '',
+});
+
+// 当前编辑的站点ID
+const currentEditSiteId = ref('');
+
+// 编辑表单验证规则
+const { validate: validateEdit, validateInfos: validateEditInfos } = useForm(editFormData, {
+ name: [
+ { required: true, message: t('page.carddata.nameRequired') },
+ {
+ pattern: /^[^ \+\-\@\=]$|^[^ \+\-\@\=].{0,62}[^ ]$/,
+ message: t('page.carddata.nameInvalid')
+ }
+ ],
+ region: [{ required: true, message: t('page.carddata.regionRequired') }],
+ timeZone: [{ required: true, message: t('page.carddata.timeZoneRequired') }],
+ scenario: [{ required: true, message: t('page.carddata.scenarioRequired') }]
+});
+
+// 处理编辑按钮点击
+const handleEdit = async (record: Api.DashboardSite) => {
+ try {
+ currentEditSiteId.value = record.siteId;
+ const response = await getSiteConfig(record.siteId);
+ if (response.data) {
+ editFormData.value = {
+ name: response.data.name,
+ region: response.data.region,
+ timeZone: response.data.timeZone,
+ scenario: response.data.scenario
+ };
+ showEditDialog.value = true;
+ }
+ } catch (error) {
+ console.error('Get site config failed:', error);
+ message.error(t('page.carddata.getConfigFailed'));
+ }
+};
+
+// 处理更新站点
+const handleUpdateSite = async () => {
+ try {
+ await validateEdit();
+ await updateSite(currentEditSiteId.value, editFormData.value);
+ message.success(t('page.carddata.updateSuccess'));
+ showEditDialog.value = false;
+ fetchSiteList(); // 刷新列表
+ } catch (error) {
+ console.error('Update site failed:', error);
+ message.error(t('page.carddata.updateFailed'));
+ }
+};
@@ -155,18 +321,12 @@ const handlePageSizeChange = (size: number) => {
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {{ t('page.carddata.addsite') }}
+
@@ -221,16 +381,157 @@ const handlePageSizeChange = (size: number) => {