【feat】 优化时间组件样式

This commit is contained in:
Kris 2025-12-14 18:16:08 +08:00
parent c3492791f3
commit 39b80c307a
6 changed files with 512 additions and 247 deletions

View File

@ -0,0 +1,506 @@
# SalesforceLoginController 方法调用说明文档
## 1. 概述
SalesforceLoginController提供了一套完整的Salesforce登录相关API支持多种登录方式包括用户名密码登录、JWT登录、客户端凭证登录和OAuth登录。
## 2. API基础信息
- 基础URL: `/salesforce/login`
- 请求格式: JSON (POST请求)
- 响应格式: JSON
- 统一响应格式:
```json
{
"code": 200, // 响应状态码
"msg": "Success", // 响应消息
"data": {} // 响应数据
}
```
## 3. 详细方法说明
### 3.1 登录Salesforce
**方法名称**: `login`
**HTTP请求**: `POST /salesforce/login/login`
**功能说明**: 支持多种登录方式根据loginType动态选择
**请求参数**:
| 参数名 | 类型 | 必须 | 说明 | 示例值 |
|--------|------|------|------|--------|
| loginType | String | 是 | 登录类型可选值password, jwt, client_credentials, oauth | "password" |
| username | String | 否 | 用户名password和jwt登录方式必填 | "user@example.com" |
| password | String | 否 | 密码password登录方式必填 | "password123" |
| securityToken | String | 否 | 安全令牌password登录方式可选 | "SECURITY_TOKEN" |
| clientId | String | 否 | 客户端IDjwt和client_credentials登录方式必填 | "CLIENT_ID" |
| clientSecret | String | 否 | 客户端密钥client_credentials登录方式必填 | "CLIENT_SECRET" |
| jwtToken | String | 否 | JWT令牌jwt登录方式可选 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |
| privateKeyPath | String | 否 | 私钥路径jwt登录方式可选 | "/path/to/private.key" |
| privateKeyPassword | String | 否 | 私钥密码jwt登录方式可选 | "KEY_PASSWORD" |
| environment | String | 否 | 环境类型可选值production, sandbox, custom默认production | "production" |
| customDomain | String | 否 | 自定义域名environment为custom时必填 | "custom.salesforce.com" |
**响应示例**:
```json
{
"code": 200,
"msg": "Login successful",
"data": {
"accessToken": "00D5f0000000001!AQwAQG...",
"instanceUrl": "https://na152.salesforce.com",
"idToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "5Aep861q6x...",
"expiresAt": "2025-12-10T12:00:00",
"success": true,
"loginType": "password",
"username": "user@example.com"
}
}
```
**调用示例**:
```javascript
// 用户名密码登录
fetch('/salesforce/login/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
loginType: 'password',
username: 'user@example.com',
password: 'password123',
environment: 'production'
})
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.2 刷新访问令牌
**方法名称**: `refreshToken`
**HTTP请求**: `POST /salesforce/login/refresh-token`
**功能说明**: 根据登录类型刷新访问令牌
**请求参数**:
| 参数名 | 类型 | 必须 | 说明 | 示例值 |
|--------|------|------|------|--------|
| refreshToken | String | 是 | 刷新令牌 | "5Aep861q6x..." |
| loginType | String | 是 | 登录类型 | "password" |
**响应示例**:
```json
{
"code": 200,
"msg": "Token refreshed successfully",
"data": {
"accessToken": "00D5f0000000001!AQwAQG...",
"instanceUrl": "https://na152.salesforce.com",
"expiresAt": "2025-12-10T12:00:00",
"success": true,
"loginType": "password"
}
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/refresh-token?refreshToken=5Aep861q6x...&loginType=password', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.3 登出Salesforce
**方法名称**: `logout`
**HTTP请求**: `POST /salesforce/login/logout`
**功能说明**: 登出并清除登录状态
**请求参数**:
| 参数名 | 类型 | 必须 | 说明 | 示例值 |
|--------|------|------|------|--------|
| accessToken | String | 是 | 访问令牌 | "00D5f0000000001!AQwAQG..." |
| loginType | String | 是 | 登录类型 | "password" |
**响应示例**:
```json
{
"code": 200,
"msg": "Logout successful",
"data": null
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/logout?accessToken=00D5f0000000001!AQwAQG...&loginType=password', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.4 获取当前登录状态
**方法名称**: `getLoginStatus`
**HTTP请求**: `GET /salesforce/login/status`
**功能说明**: 获取当前的登录状态信息
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "Login status retrieved",
"data": {
"accessToken": "00D5f0000000001!AQwAQG...",
"instanceUrl": "https://na152.salesforce.com",
"loginType": "password",
"username": "user@example.com",
"success": true,
"sessionValid": true
}
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/status', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.5 清除登录状态
**方法名称**: `clearLoginStatus`
**HTTP请求**: `POST /salesforce/login/clear-status`
**功能说明**: 清除当前的登录状态信息
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "Login status cleared successfully",
"data": null
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/clear-status', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.6 获取支持的登录类型
**方法名称**: `getSupportedLoginTypes`
**HTTP请求**: `GET /salesforce/login/supported-types`
**功能说明**: 获取系统支持的所有登录方式类型
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "Supported login types retrieved",
"data": ["password", "jwt", "client_credentials", "oauth"]
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/supported-types', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
### 3.7 验证会话有效性
**方法名称**: `validateSession`
**HTTP请求**: `GET /salesforce/login/validate-session`
**功能说明**: 验证当前会话是否有效
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "Session is valid",
"data": {
"accessToken": "00D5f0000000001!AQwAQG...",
"instanceUrl": "https://na152.salesforce.com",
"loginType": "password",
"username": "user@example.com",
"success": true,
"sessionValid": true
}
}
```
**调用示例**:
```javascript
fetch('/salesforce/login/validate-session', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
## 4. 前端调用最佳实践
### 4.1 登录流程
1. 获取支持的登录类型:`GET /salesforce/login/supported-types`
2. 根据用户选择的登录方式,构建相应的登录请求
3. 调用登录接口:`POST /salesforce/login/login`
4. 保存登录结果中的accessToken和refreshToken
5. 定期调用会话验证接口:`GET /salesforce/login/validate-session`
6. 当token过期时调用刷新token接口`POST /salesforce/login/refresh-token`
7. 用户登出时,调用登出接口:`POST /salesforce/login/logout`
### 4.2 错误处理
- 检查响应的code字段非200表示错误
- 处理常见错误:
- 401 Unauthorized登录信息无效
- 403 Forbidden权限不足
- 500 Internal Server Error服务器内部错误
- 503 Service Unavailable服务不可用
### 4.3 安全建议
- 敏感信息如密码、token不要明文存储在前端
- 使用HTTPS协议传输所有请求
- 定期更新token避免token过期
- 登出时确保清除所有本地存储的token信息
## 5. 示例代码
### 5.1 登录示例Vue 3
```vue
<template>
<div>
<h1>Salesforce Login</h1>
<div>
<label for="loginType">Login Type:</label>
<select v-model="loginType">
<option value="password">Password</option>
<option value="jwt">JWT</option>
<option value="client_credentials">Client Credentials</option>
</select>
</div>
<div v-if="loginType === 'password'">
<div>
<label for="username">Username:</label>
<input type="text" v-model="username" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" v-model="password" />
</div>
</div>
<button @click="login">Login</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">{{ success }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const loginType = ref('password');
const username = ref('');
const password = ref('');
const error = ref('');
const success = ref('');
const login = async () => {
try {
const response = await fetch('/salesforce/login/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
loginType: loginType.value,
username: username.value,
password: password.value
})
});
const data = await response.json();
if (data.code === 200) {
success.value = 'Login successful';
error.value = '';
// 保存token等信息
localStorage.setItem('salesforceAccessToken', data.data.accessToken);
localStorage.setItem('salesforceRefreshToken', data.data.refreshToken);
} else {
error.value = data.msg;
success.value = '';
}
} catch (err) {
error.value = 'Login failed: ' + err.message;
success.value = '';
}
};
</script>
```
### 5.2 会话验证示例React
```javascript
import React, { useEffect, useState } from 'react';
const SessionValidator = () => {
const [sessionValid, setSessionValid] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
const validateSession = async () => {
try {
const response = await fetch('/salesforce/login/validate-session', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
setSessionValid(data.code === 200 && data.data.sessionValid);
} catch (err) {
setSessionValid(false);
} finally {
setLoading(false);
}
};
validateSession();
// 每5分钟验证一次会话
const interval = setInterval(validateSession, 5 * 60 * 1000);
return () => clearInterval(interval);
}, []);
if (loading) {
return <div>Checking session...</div>;
}
return (
<div>
<h2>Session Status: {sessionValid ? 'Valid' : 'Invalid'}</h2>
{!sessionValid && (
<button onClick={() => window.location.href = '/login'}>
Login Again
</button>
)}
</div>
);
};
export default SessionValidator;
```
## 6. 常见问题
### 6.1 如何选择合适的登录方式?
- **password登录**:适用于开发测试环境,需要用户名和密码
- **jwt登录**:适用于机器到机器的集成,安全性较高
- **client_credentials登录**:适用于服务器到服务器的集成
- **oauth登录**:适用于第三方应用集成,支持授权码流程
### 6.2 token过期怎么办
当token过期时系统会自动检测并返回sessionValid: false此时需要
1. 调用`POST /salesforce/login/refresh-token`接口刷新token
2. 如果刷新失败,需要重新登录
### 6.3 如何获取当前登录用户信息?
调用`GET /salesforce/login/status`接口响应中包含username和其他用户信息
### 6.4 如何处理不同环境?
通过environment参数指定环境类型
- production生产环境
- sandbox沙盒环境
- custom自定义环境需要同时提供customDomain参数
## 7. 版本说明
- 当前版本1.0.0
- 支持的Salesforce API版本61.0.0
## 8. 联系方式
如有问题,请联系系统管理员或开发团队。

View File

@ -42,20 +42,3 @@ export function delConfiguration(configId) {
method: 'delete'
})
}
// 刷新配置缓存
export function refreshConfigurationCache() {
return request({
url: '/setting/configuration/refresh',
method: 'post'
})
}
// 验证配置值
export function validateConfiguration(data) {
return request({
url: '/setting/configuration/validate',
method: 'post',
data: data
})
}

View File

@ -42,28 +42,3 @@ export function delVersion(versionId) {
method: 'delete'
})
}
// 发布配置版本
export function publishVersion(versionId) {
return request({
url: '/setting/version/' + versionId + '/publish',
method: 'post'
})
}
// 创建配置快照
export function createSnapshot(data) {
return request({
url: '/setting/version/snapshot',
method: 'post',
data: data
})
}
// 回滚到指定版本
export function rollbackVersion(versionId) {
return request({
url: '/setting/version/' + versionId + '/rollback',
method: 'post'
})
}

View File

@ -10,14 +10,6 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="统计日期" prop="statDate">
<el-date-picker clearable
v-model="queryParams.statDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择统计日期">
</el-date-picker>
</el-form-item>
<el-form-item label="统计小时" prop="statHour">
<el-input
v-model="queryParams.statHour"
@ -111,11 +103,7 @@
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="统计ID" align="center" prop="statId" />
<el-table-column label="租户编号" align="center" prop="tenantId" />
<el-table-column label="统计日期" align="center" prop="statDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.statDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="统计日期" align="center" prop="statDate" />
<el-table-column label="统计小时" align="center" prop="statHour" />
<el-table-column label="登录类型" align="center" prop="loginType" />
<el-table-column label="成功次数" align="center" prop="successCount" />
@ -145,14 +133,6 @@
<el-form-item label="租户编号" prop="tenantId">
<el-input v-model="form.tenantId" placeholder="请输入租户编号" />
</el-form-item>
<el-form-item label="统计日期" prop="statDate">
<el-date-picker clearable
v-model="form.statDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择统计日期">
</el-date-picker>
</el-form-item>
<el-form-item label="统计小时" prop="statHour">
<el-input v-model="form.statHour" placeholder="请输入统计小时" />
</el-form-item>

View File

@ -96,26 +96,6 @@
v-hasPermi="['setting:configuration:export']"
>导出</el-button>
</el-col>
<!-- 新增的按钮 -->
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Refresh"
@click="handleRefreshCache"
v-hasPermi="['setting:configuration:refresh']"
>刷新缓存</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="DocumentChecked"
:disabled="single"
@click="handleValidateConfig"
v-hasPermi="['setting:configuration:validate']"
>验证配置</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -186,29 +166,11 @@
</div>
</template>
</el-dialog>
<!-- 配置验证对话框 -->
<el-dialog title="验证配置值" v-model="validateOpen" width="500px" append-to-body>
<el-form ref="validateRef" :model="validateForm" :rules="validateRules" label-width="80px">
<el-form-item label="配置键" prop="configKey">
<el-input v-model="validateForm.configKey" placeholder="请输入配置键" />
</el-form-item>
<el-form-item label="配置值" prop="configValue">
<el-input v-model="validateForm.configValue" type="textarea" placeholder="请输入配置值" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitValidateForm"> </el-button>
<el-button @click="cancelValidate"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Configuration">
import { listConfiguration, getConfiguration, delConfiguration, addConfiguration, updateConfiguration, refreshConfigurationCache, validateConfiguration } from "@/api/setting/configuration";
import { listConfiguration, getConfiguration, delConfiguration, addConfiguration, updateConfiguration } from "@/api/setting/configuration";
const { proxy } = getCurrentInstance();
@ -222,17 +184,6 @@ const multiple = ref(true);
const total = ref(0);
const title = ref("");
const validateOpen = ref(false);
const validateForm = ref({});
const validateRules = ref({
configKey: [
{ required: true, message: "配置键不能为空", trigger: "blur" }
],
configValue: [
{ required: true, message: "配置值不能为空", trigger: "blur" }
]
});
const data = reactive({
form: {},
queryParams: {
@ -314,15 +265,6 @@ function reset() {
proxy.resetForm("configurationRef");
}
//
function resetValidate() {
validateForm.value = {
configKey: null,
configValue: null
};
proxy.resetForm("validateRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
@ -392,6 +334,8 @@ function handleDelete(row) {
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('setting/configuration/export', {
@ -399,43 +343,5 @@ function handleExport() {
}, `configuration_${new Date().getTime()}.xlsx`)
}
//
function cancelValidate() {
validateOpen.value = false;
resetValidate();
}
/** 刷新配置缓存按钮操作 */
function handleRefreshCache() {
proxy.$modal.confirm('是否确认刷新配置缓存?').then(function() {
return refreshConfigurationCache();
}).then(() => {
proxy.$modal.msgSuccess("配置缓存刷新成功");
}).catch(() => {});
}
/** 验证配置值按钮操作 */
function handleValidateConfig(row) {
resetValidate();
const config = row || (ids.value.length > 0 ? configurationList.value.find(item => item.configId === ids.value[0]) : null);
if (config) {
validateForm.value.configKey = config.configKey;
validateForm.value.configValue = config.configValue;
}
validateOpen.value = true;
}
/** 提交验证表单 */
function submitValidateForm() {
proxy.$refs["validateRef"].validate(valid => {
if (valid) {
validateConfiguration(validateForm.value).then(response => {
proxy.$modal.msgSuccess("配置值验证通过");
validateOpen.value = false;
});
}
});
}
getList();
</script>
</script>

View File

@ -80,36 +80,6 @@
v-hasPermi="['setting:version:export']"
>导出</el-button>
</el-col>
<!-- 新增的按钮 -->
<el-col :span="1.5">
<el-button
type="success"
plain
icon="UploadFilled"
@click="handleCreateSnapshot"
v-hasPermi="['setting:version:snapshot']"
>创建快照</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="RefreshRight"
:disabled="single"
@click="handlePublish"
v-hasPermi="['setting:version:publish']"
>发布版本</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="RefreshLeft"
:disabled="single"
@click="handleRollback"
v-hasPermi="['setting:version:rollback']"
>回滚版本</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -182,26 +152,11 @@
</div>
</template>
</el-dialog>
<!-- 创建快照对话框 -->
<el-dialog :title="snapshotTitle" v-model="snapshotOpen" width="500px" append-to-body>
<el-form ref="snapshotRef" :model="snapshotForm" :rules="snapshotRules" label-width="80px">
<el-form-item label="版本描述" prop="versionDesc">
<el-input v-model="snapshotForm.versionDesc" type="textarea" placeholder="请输入版本描述" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitSnapshotForm"> </el-button>
<el-button @click="cancelSnapshot"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Version">
import { listVersion, getVersion, delVersion, addVersion, updateVersion, publishVersion, createSnapshot, rollbackVersion } from "@/api/setting/version";
import { listVersion, getVersion, delVersion, addVersion, updateVersion } from "@/api/setting/version";
const { proxy } = getCurrentInstance();
@ -357,47 +312,7 @@ function handleDelete(row) {
}).catch(() => {});
}
/** 发布版本按钮操作 */
function handlePublish(row) {
const versionId = row.versionId || ids.value[0];
proxy.$modal.confirm('是否确认发布版本编号为"' + versionId + '"的配置版本?').then(function() {
return publishVersion(versionId);
}).then(() => {
getList();
proxy.$modal.msgSuccess("发布成功");
}).catch(() => {});
}
/** 创建快照按钮操作 */
function handleCreateSnapshot() {
resetSnapshot();
snapshotOpen.value = true;
snapshotTitle.value = "创建配置快照";
}
/** 提交快照表单 */
function submitSnapshotForm() {
proxy.$refs["snapshotRef"].validate(valid => {
if (valid) {
createSnapshot(snapshotForm.value).then(response => {
proxy.$modal.msgSuccess("创建快照成功");
snapshotOpen.value = false;
getList();
});
}
});
}
/** 回滚版本按钮操作 */
function handleRollback(row) {
const versionId = row.versionId || ids.value[0];
proxy.$modal.confirm('是否确认回滚到版本编号为"' + versionId + '"的配置版本?此操作不可逆!').then(function() {
return rollbackVersion(versionId);
}).then(() => {
getList();
proxy.$modal.msgSuccess("回滚成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {