Compare commits
No commits in common. "v2" and "master" have entirely different histories.
76
api/pom.xml
76
api/pom.xml
@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.lktx.center</groupId>
|
||||
<artifactId>app-center</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>api</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- web框架 -->
|
||||
<dependency>
|
||||
<artifactId>hserver-web-starter</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>hserver-plugin-forest</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>hserver-plugin-satoken</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId>
|
||||
<version>1.16.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>33.4.8-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.25</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>hserver-maven</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -1,14 +0,0 @@
|
||||
package com.lktx.center;
|
||||
|
||||
|
||||
import cn.hserver.core.boot.HServerApplication;
|
||||
import cn.hserver.core.boot.annotation.HServerBoot;
|
||||
import cn.hserver.mvc.server.WebServer;
|
||||
|
||||
@HServerBoot
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
WebServer.webPort(8981);
|
||||
HServerApplication.run(Main.class, args);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package com.lktx.center.config;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.hserver.core.ioc.annotation.Bean;
|
||||
import cn.hserver.core.ioc.annotation.Component;
|
||||
import cn.hserver.mvc.common.JsonResult;
|
||||
import cn.hserver.mvc.context.WebContext;
|
||||
import cn.hserver.mvc.exception.GlobalExceptionHandler;
|
||||
|
||||
@Component
|
||||
public class AllException extends GlobalExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handlerException(Throwable throwable, WebContext webContext) {
|
||||
NotLoginException exception = getException(throwable, NotLoginException.class);
|
||||
if (exception!=null ){
|
||||
webContext.response.sendJson(JsonResult.error(-2, exception.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package com.lktx.center.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.hserver.core.ioc.annotation.Autowired;
|
||||
import cn.hserver.mvc.annotation.Controller;
|
||||
import cn.hserver.mvc.annotation.router.GET;
|
||||
import cn.hserver.mvc.common.JsonResult;
|
||||
import com.lktx.center.service.AppCenterService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Controller("/app-center")
|
||||
public class AppCenterController {
|
||||
|
||||
@Autowired
|
||||
private AppCenterService appCenterService;
|
||||
|
||||
@GET("/list")
|
||||
@SaCheckLogin
|
||||
public JsonResult list(){
|
||||
Map<String, Object> appList = appCenterService.getAppList();
|
||||
if (appList != null) {
|
||||
return JsonResult.ok().put("data", appList);
|
||||
}
|
||||
return JsonResult.error();
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
package com.lktx.center.controller;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hserver.core.ioc.annotation.Autowired;
|
||||
import cn.hserver.mvc.annotation.Controller;
|
||||
import cn.hserver.mvc.annotation.router.GET;
|
||||
import cn.hserver.mvc.annotation.router.RequestMapping;
|
||||
import cn.hserver.mvc.common.JsonResult;
|
||||
import cn.hserver.mvc.request.Request;
|
||||
import cn.hserver.mvc.response.Response;
|
||||
import com.lktx.center.config.Data;
|
||||
import com.lktx.center.config.SsoAuthRequest;
|
||||
import com.lktx.center.domain.vo.LoginInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthCallback;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthToken;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
|
||||
@Slf4j
|
||||
@Controller("/oauth")
|
||||
public class RestAuthController {
|
||||
|
||||
@Autowired
|
||||
private SsoAuthRequest authRequest;
|
||||
|
||||
@RequestMapping("/render")
|
||||
public void renderAuth(Response response) {
|
||||
String authorize = authRequest.authorize(AuthStateUtils.createState());
|
||||
response.redirect(authorize);
|
||||
}
|
||||
|
||||
@RequestMapping("/callback")
|
||||
public JsonResult login(AuthCallback callback, Request request) {
|
||||
try {
|
||||
String rawData = request.getRawData();
|
||||
System.out.println(rawData);
|
||||
AuthResponse<AuthUser> login = authRequest.login(callback);
|
||||
AuthUser data = login.getData();
|
||||
AuthToken token = login.getData().getToken();
|
||||
StpUtil.login(login.getData().getUuid());
|
||||
SaSession session = StpUtil.getSession();
|
||||
session.set(Data.AuthToken, token);
|
||||
|
||||
LoginInfo build = LoginInfo.builder()
|
||||
.userId(data.getUuid())
|
||||
.avatar(data.getAvatar())
|
||||
.username(data.getUsername())
|
||||
.nickname(data.getNickname())
|
||||
.token(StpUtil.getTokenInfo().tokenValue)
|
||||
.build();
|
||||
|
||||
return JsonResult.ok().put("data", build);
|
||||
}catch (Exception e) {
|
||||
log.error("login error",e);
|
||||
}
|
||||
return JsonResult.error();
|
||||
}
|
||||
|
||||
|
||||
@GET("/logout")
|
||||
public JsonResult logout() {
|
||||
System.out.println(StpUtil.getSession().getId());
|
||||
if (StpUtil.isLogin()){
|
||||
SaSession session = StpUtil.getSession();
|
||||
AuthToken authToken = session.get(Data.AuthToken,null);
|
||||
if (authToken != null){
|
||||
AuthResponse<Void> revoke = authRequest.revoke(authToken);
|
||||
System.out.println(revoke.getMsg());
|
||||
}
|
||||
//子系统退出
|
||||
StpUtil.logout();
|
||||
}
|
||||
return JsonResult.ok();
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package com.lktx.center.domain.vo;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class LoginInfo {
|
||||
private String userId;
|
||||
private String nickname;
|
||||
private String username;
|
||||
private String avatar;
|
||||
private String token;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package com.lktx.center.filter;
|
||||
|
||||
import cn.hserver.core.ioc.annotation.Bean;
|
||||
import cn.hserver.core.ioc.annotation.Component;
|
||||
import cn.hserver.core.ioc.annotation.Order;
|
||||
import cn.hserver.mvc.constants.HttpMethod;
|
||||
import cn.hserver.mvc.context.WebContext;
|
||||
import cn.hserver.mvc.filter.FilterAdapter;
|
||||
|
||||
@Component
|
||||
@Order(1)
|
||||
public class CorsFilter implements FilterAdapter {
|
||||
@Override
|
||||
public void doFilter(WebContext webkit) throws Exception {
|
||||
webkit.response.addHeader("Access-Control-Allow-Origin", "*");
|
||||
webkit.response.addHeader("Access-Control-Allow-Methods", "*");
|
||||
webkit.response.addHeader("Access-Control-Allow-Credentials", "*");
|
||||
webkit.response.addHeader("Access-Control-Allow-Headers", "*");
|
||||
if (webkit.request.getRequestMethod()== HttpMethod.OPTIONS) {
|
||||
webkit.response.sendHtml("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package com.lktx.center.service;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hserver.core.ioc.annotation.Autowired;
|
||||
import cn.hserver.core.ioc.annotation.Component;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.lktx.center.config.Data;
|
||||
import com.lktx.center.config.SsoAuthRequest;
|
||||
import com.lktx.center.domain.bean.SsoApp;
|
||||
import com.lktx.center.domain.vo.SsoUserAppVO;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthToken;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class AppCenterService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private SsoAuthRequest ssoAuthRequest;
|
||||
|
||||
|
||||
private final Cache<String, Map<String,Object>> expiringCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES) // 写入后30分钟过期
|
||||
.maximumSize(1)
|
||||
.build();
|
||||
|
||||
public Map<String,Object> getAppList(){
|
||||
Map<String, Object> appList = expiringCache.getIfPresent("appList");
|
||||
if(appList != null){
|
||||
return appList;
|
||||
}
|
||||
SaSession session = StpUtil.getSession();
|
||||
AuthToken authToken = session.get(Data.AuthToken,null);
|
||||
if (authToken != null){
|
||||
AuthResponse<SsoUserAppVO> center = ssoAuthRequest.center(authToken);
|
||||
if (center.ok()) {
|
||||
Map<String, Object> data = Map.of(
|
||||
"user", center.getData().getSsoUser(),
|
||||
"appList", center.getData().getSsoAppList(),
|
||||
"appGroup", center.getData().getSsoAppList().stream().map(SsoApp::getSsoAppGroup).collect(Collectors.toSet())
|
||||
);
|
||||
expiringCache.put("appList", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
63
pom.xml
63
pom.xml
@ -7,15 +7,10 @@
|
||||
<groupId>com.lktx.center</groupId>
|
||||
<artifactId>app-center</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>web</module>
|
||||
</modules>
|
||||
<parent>
|
||||
<artifactId>hserver-parent</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
<version>4.0.0-beta.5</version>
|
||||
<version>3.7.0</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
@ -25,6 +20,62 @@
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心依赖-->
|
||||
<dependency>
|
||||
<artifactId>hserver</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
<!-- web框架 -->
|
||||
<dependency>
|
||||
<artifactId>hserver-plugin-web</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>hserver-plugin-forest</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<artifactId>hserver-plugin-satoken</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId>
|
||||
<version>1.16.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.25</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>hserver-plugin-maven</artifactId>
|
||||
<groupId>cn.hserver</groupId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
12
src/main/java/com/lktx/center/Main.java
Normal file
12
src/main/java/com/lktx/center/Main.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.lktx.center;
|
||||
|
||||
|
||||
import cn.hserver.HServerApplication;
|
||||
import cn.hserver.core.ioc.annotation.HServerBoot;
|
||||
|
||||
@HServerBoot
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
HServerApplication.run(Main.class,8981, args);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
package com.lktx.center.config;
|
||||
|
||||
import cn.hserver.core.config.annotation.Configuration;
|
||||
import cn.hserver.core.config.annotation.Value;
|
||||
import cn.hserver.core.ioc.annotation.Bean;
|
||||
import cn.hserver.core.ioc.annotation.Configuration;
|
||||
import cn.hserver.core.ioc.annotation.Value;
|
||||
import me.zhyd.oauth.config.AuthConfig;
|
||||
|
||||
import java.util.List;
|
||||
@ -10,13 +10,13 @@ import java.util.List;
|
||||
@Configuration
|
||||
public class AuthRequestConfig {
|
||||
|
||||
@Value("oauth.client-id")
|
||||
@Value("oauth.clientId")
|
||||
private String clientId;
|
||||
|
||||
@Value("oauth.client-secret")
|
||||
@Value("oauth.clientSecret")
|
||||
private String clientSecret;
|
||||
|
||||
@Value("oauth.redirect-uri")
|
||||
@Value("oauth.redirectUri")
|
||||
private String redirectUri;
|
||||
|
||||
@Value("oauth.url")
|
||||
@ -3,9 +3,10 @@ package com.lktx.center.controller;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hserver.core.ioc.annotation.Autowired;
|
||||
import cn.hserver.mvc.annotation.Controller;
|
||||
import cn.hserver.mvc.annotation.router.GET;
|
||||
import cn.hserver.mvc.response.Response;
|
||||
import cn.hserver.plugin.web.annotation.Controller;
|
||||
import cn.hserver.plugin.web.annotation.GET;
|
||||
import cn.hserver.plugin.web.interfaces.HttpResponse;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.lktx.center.config.Data;
|
||||
import com.lktx.center.config.SsoAuthRequest;
|
||||
import com.lktx.center.domain.bean.SsoApp;
|
||||
@ -25,7 +26,7 @@ public class HomeController {
|
||||
private SsoAuthRequest authRequest;
|
||||
|
||||
@GET("/")
|
||||
public void index(Response response) {
|
||||
public void index(HttpResponse response) {
|
||||
if (StpUtil.isLogin()){
|
||||
try {
|
||||
SaSession session = StpUtil.getSession();
|
||||
@ -51,4 +52,19 @@ public class HomeController {
|
||||
}
|
||||
|
||||
|
||||
@GET("/logout")
|
||||
public void logout(HttpResponse response) {
|
||||
if (StpUtil.isLogin()){
|
||||
//可以全局退出
|
||||
SaSession session = StpUtil.getSession();
|
||||
AuthToken authToken = session.get(Data.AuthToken,null);
|
||||
if (authToken != null){
|
||||
AuthResponse revoke = authRequest.revoke(authToken);
|
||||
System.out.println(revoke.getMsg());
|
||||
}
|
||||
//子系统退出
|
||||
StpUtil.logout();
|
||||
}
|
||||
response.redirect("/");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.lktx.center.controller;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hserver.core.ioc.annotation.Autowired;
|
||||
import cn.hserver.plugin.web.annotation.Controller;
|
||||
import cn.hserver.plugin.web.annotation.RequestMapping;
|
||||
import cn.hserver.plugin.web.interfaces.HttpResponse;
|
||||
import com.lktx.center.config.Data;
|
||||
import com.lktx.center.config.SsoAuthRequest;
|
||||
import me.zhyd.oauth.model.AuthCallback;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthToken;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
|
||||
@Controller("/oauth")
|
||||
public class RestAuthController {
|
||||
|
||||
@Autowired
|
||||
private SsoAuthRequest authRequest;
|
||||
|
||||
@RequestMapping("/render")
|
||||
public void renderAuth(HttpResponse response) {
|
||||
String authorize = authRequest.authorize(AuthStateUtils.createState());
|
||||
response.redirect(authorize);
|
||||
}
|
||||
|
||||
@RequestMapping("/callback")
|
||||
public void login(AuthCallback callback,HttpResponse response) {
|
||||
try {
|
||||
AuthResponse<AuthUser> login = authRequest.login(callback);
|
||||
AuthToken token = login.getData().getToken();
|
||||
StpUtil.login(login.getData().getUuid());
|
||||
SaSession session = StpUtil.getSession();
|
||||
session.set(Data.AuthToken, token);
|
||||
response.redirect("/");
|
||||
}catch (Exception e) {
|
||||
response.redirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
oauth:
|
||||
client-id: 65013a3d89d14fab8ff3eb2c0f3981a3
|
||||
client-secret: 22b5ce70d67f41b79b27cbedb57c976a
|
||||
redirect-uri: http://127.0.0.1:5173/login
|
||||
url: http://127.0.0.1:8888/21/
|
||||
redirect-uri: http://127.0.0.1:8981/oauth/callback
|
||||
url: http://192.168.0.206:8911/21/
|
||||
BIN
src/main/resources/static/webfonts/fa-solid-900.ttf
Normal file
BIN
src/main/resources/static/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/static/webfonts/fa-solid-900.woff2
Normal file
BIN
src/main/resources/static/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
24
web/.gitignore
vendored
24
web/.gitignore
vendored
@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@ -1,13 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "app-center",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"axios": "^1.11.0",
|
||||
"element-plus": "^2.10.4",
|
||||
"font-awesome": "^4.7.0",
|
||||
"vue": "^3.5.17",
|
||||
"vue-router": "4",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
20
web/pom.xml
20
web/pom.xml
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.lktx.center</groupId>
|
||||
<artifactId>app-center</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>web</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,8 +0,0 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
<style scoped>
|
||||
</style>
|
||||
@ -1,8 +0,0 @@
|
||||
import http from '../data/http'
|
||||
|
||||
export function appCenterList() {
|
||||
return http({
|
||||
url: '/app-center/list',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import http from '../data/http'
|
||||
|
||||
export function login(data) {
|
||||
return http({
|
||||
url: '/oauth/callback',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
export function logout() {
|
||||
return http({
|
||||
url: '/oauth/logout',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@ -1,4 +0,0 @@
|
||||
//API地址
|
||||
export const host = 'http://127.0.0.1:8981'
|
||||
//登录回调调整地址
|
||||
export const login = 'http://127.0.0.1:8981/oauth/render'
|
||||
@ -1,67 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import userInfo from './userInfo.js'
|
||||
import {host} from './host.js'
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
|
||||
// baseURL: "http://127.0.0.1:9090", // url = base url + request url
|
||||
// baseURL: "http://xxx.com", // url = base url + request url
|
||||
baseURL: host, // url = base url + request url
|
||||
// withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 500000 // request timeout
|
||||
})
|
||||
|
||||
// request interceptor
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// do something before request is sent
|
||||
|
||||
if (userInfo.getUserInfo()) {
|
||||
// let each request carry token
|
||||
// ['X-Token'] is a custom headers key
|
||||
// please modify it according to the actual situation
|
||||
config.headers['satoken'] = userInfo.getUserInfo().token
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// do something with request error
|
||||
console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
/**
|
||||
* If you want to get http information such as headers or status
|
||||
* Please return response => response
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine the request status by custom code
|
||||
* Here is just an example
|
||||
* You can also judge the status by HTTP Status Code
|
||||
*/
|
||||
response => {
|
||||
const res = response.data
|
||||
// if the custom code is not 20000, it is judged as an error.
|
||||
if (res.code !== 200) {
|
||||
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
|
||||
if (res.code === -2) {
|
||||
location.href = "/login"
|
||||
return null
|
||||
}
|
||||
return res
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log('err' + error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
||||
@ -1,16 +0,0 @@
|
||||
export default {
|
||||
KEY: "USER_INFO",
|
||||
setUserInfo(userInfo) {
|
||||
localStorage.setItem(this.KEY, JSON.stringify(userInfo))
|
||||
},
|
||||
getUserInfo() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(this.KEY))
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
removeUserInfo(){
|
||||
localStorage.removeItem(this.KEY)
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router/index.js'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'font-awesome/css/font-awesome.min.css'
|
||||
createApp(App).use(router).use(ElementPlus).mount('#app')
|
||||
@ -1,36 +0,0 @@
|
||||
import {createMemoryHistory, createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
|
||||
|
||||
import index from '../views/index.vue'
|
||||
import login from '../views/login.vue'
|
||||
import home from '../views/home.vue'
|
||||
import appcenter from '../views/appcenter.vue'
|
||||
import news from '../views/news.vue'
|
||||
import announcement from '../views/announcement.vue'
|
||||
import staffstyle from '../views/staffstyle.vue'
|
||||
import chat from '../views/chat.vue'
|
||||
import suggestionbox from '../views/suggestionbox.vue'
|
||||
|
||||
const routes = [
|
||||
{path: '/login', component: login},
|
||||
{
|
||||
path: '/', component: index,
|
||||
children:[
|
||||
{path: '', redirect: '/home'},
|
||||
{ path: '/home', component: home },
|
||||
{ path: '/app-center', component: appcenter },
|
||||
{ path: '/news', component: news },
|
||||
{ path: '/announcement', component: announcement },
|
||||
{ path: '/staff-style', component: staffstyle },
|
||||
{ path: '/chat', component: chat },
|
||||
{ path: '/suggestion-box', component: suggestionbox }
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default router;
|
||||
@ -1,12 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
/*
|
||||
* 配置教程 https://juejin.cn/post/7480450288421109787
|
||||
*/
|
||||
@theme inline {
|
||||
--color-primary: #085ce6;
|
||||
--color-secondary: #FFB81C;
|
||||
--color-dark: #12192C;
|
||||
--color-light: #F5F7FA;
|
||||
--color-muted: #6B7280;
|
||||
--font-display: 'Inter', 'system-ui', 'sans-serif';
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElCard, ElTag, ElDivider } from 'element-plus';
|
||||
|
||||
// 模拟公告数据
|
||||
const announcements = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '系统维护通知',
|
||||
content: '为了给大家提供更稳定、高效的服务,本系统将于本周日凌晨 02:00 - 04:00 进行维护,届时系统将暂停服务,请您提前做好相应准备,感谢您的理解与支持!',
|
||||
date: '2025-07-25',
|
||||
type: '维护'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '假期安排通知',
|
||||
content: '根据国家法定节假日安排,结合公司实际情况,现将今年国庆节放假安排通知如下:10 月 1 日至 10 月 7 日放假调休,共 7 天。10 月 8 日(星期六)、10 月 9 日(星期日)上班。请各位员工提前做好工作安排。',
|
||||
date: '2025-07-24',
|
||||
type: '假期'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '新政策发布',
|
||||
content: '公司新的绩效考核政策已经正式发布,该政策将从下个月开始实施。请各位员工仔细阅读政策文件,如有疑问可随时咨询人力资源部门。',
|
||||
date: '2025-07-23',
|
||||
type: '政策'
|
||||
}
|
||||
]);
|
||||
|
||||
const getTagType = (type: string) => {
|
||||
switch (type) {
|
||||
case '维护':
|
||||
return 'warning';
|
||||
case '假期':
|
||||
return 'success';
|
||||
case '政策':
|
||||
return 'info';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-8 text-center">企业公告</h1>
|
||||
<div class="space-y-6">
|
||||
<ElCard v-for="announcement in announcements" :key="announcement.id" class="shadow-md hover:shadow-xl transition-shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-700">{{ announcement.title }}</h2>
|
||||
<el-tag :type="getTagType(announcement.type)" size="small">{{ announcement.type }}</el-tag>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<p class="text-gray-600 mb-4">{{ announcement.content }}</p>
|
||||
<div class="text-right text-sm text-gray-500">发布时间:{{ announcement.date }}</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,510 +0,0 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 p-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-2">应用中心</h1>
|
||||
<p class="text-gray-600">发现和使用企业内部的各类应用系统</p>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-8">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div class="relative w-full md:w-1/3">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索应用..."
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-button
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:type="activeCategory === category.id ? 'primary' : 'text'"
|
||||
@click="activeCategory = category.id"
|
||||
>
|
||||
{{ category.name }}
|
||||
</el-button>
|
||||
<el-button type="text" @click="activeCategory = 'all'">全部</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的应用 -->
|
||||
<div class="mb-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-800">我的应用</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-tooltip content="拖拽可调整顺序" placement="top">
|
||||
<el-button type="text" icon="el-icon-rank" size="small"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="管理我的应用" placement="top">
|
||||
<el-button type="text" icon="el-icon-setting" @click="showManageMyApps = true"></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用draggable实现拖拽排序 -->
|
||||
|
||||
|
||||
<draggable
|
||||
v-model="myApps"
|
||||
group="people"
|
||||
@start="drag=true"
|
||||
@end="onDragEnd"
|
||||
class="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4"
|
||||
|
||||
>
|
||||
<template #item="{element}">
|
||||
<el-card
|
||||
class="app-card cursor-pointer hover:shadow-lg transition-shadow duration-300 relative"
|
||||
@click="openApp(element)"
|
||||
>
|
||||
<div class="absolute top-2 left-2 drag-handle cursor-move opacity-50 hover:opacity-100 transition-opacity mt-1">
|
||||
<el-icon size="16"><Rank /></el-icon>
|
||||
</div>
|
||||
<!-- 卡片内容保持不变 -->
|
||||
<div class="absolute top-2 right-2">
|
||||
<el-button
|
||||
type="text"
|
||||
:icon="Delete"
|
||||
@click.stop="confirmRemoveFromMyApps(element)"
|
||||
></el-button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center p-4">
|
||||
<div class="w-14 h-14 rounded-lg bg-primary/10 flex items-center justify-center mb-3">
|
||||
<el-avatar :src="element.icon" />
|
||||
</div>
|
||||
<h3 class="font-medium text-gray-800 mb-1 text-center truncate w-full">{{ element.name }}</h3>
|
||||
<p class="text-xs text-gray-500 text-center truncate w-full">{{ element.description }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
|
||||
|
||||
<!-- 没有我的应用时的提示 -->
|
||||
<div
|
||||
v-if="myApps.length === 0 && !searchQuery.trim()"
|
||||
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||
>
|
||||
<el-icon class="text-4xl mb-3"><Collection /></el-icon>
|
||||
<p>您还没有添加任何应用到"我的应用"</p>
|
||||
<el-button type="text" size="small" @click="showManageMyApps = true">添加应用</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索无结果提示 -->
|
||||
<div
|
||||
v-if="filteredMyApps.length === 0 && searchQuery.trim()"
|
||||
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||
>
|
||||
<el-icon class="text-4xl mb-3"><Search /></el-icon>
|
||||
<p>没有找到匹配的应用</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全部应用 -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-800">全部应用</h2>
|
||||
<span class="text-sm text-gray-500">{{ filteredAllApps.length }} 个应用</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||
<el-card
|
||||
v-for="app in filteredAllApps"
|
||||
:key="app.id"
|
||||
class="app-card cursor-pointer hover:shadow-lg transition-shadow duration-300"
|
||||
@click="openApp(app)"
|
||||
>
|
||||
<div class="flex flex-col items-center p-4">
|
||||
<div class="w-14 h-14 rounded-lg bg-primary/10 flex items-center justify-center mb-3">
|
||||
<el-avatar :src="app.icon" />
|
||||
</div>
|
||||
<h3 class="font-medium text-gray-800 mb-1 text-center truncate w-full">{{ app.name }}</h3>
|
||||
<p class="text-xs text-gray-500 text-center truncate w-full">{{ app.description }}</p>
|
||||
|
||||
<!-- 添加到我的应用按钮 -->
|
||||
<el-button
|
||||
v-if="!isAppInMyApps(app.id)"
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
class="mt-2"
|
||||
@click.stop="addToMyApps(app)"
|
||||
>
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 没有应用时的提示 -->
|
||||
<div
|
||||
v-if="filteredAllApps.length === 0"
|
||||
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||
>
|
||||
<el-icon class="text-4xl mb-3"><Search /></el-icon>
|
||||
<p>没有找到匹配的应用</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理我的应用对话框 -->
|
||||
<el-dialog
|
||||
title="管理我的应用"
|
||||
:visible.sync="showManageMyApps"
|
||||
width="40%"
|
||||
:before-close="handleCloseManageMyApps"
|
||||
>
|
||||
<template #content>
|
||||
<div class="p-4">
|
||||
<p class="text-sm text-gray-600 mb-4">从下方选择您常用的应用添加到"我的应用"</p>
|
||||
|
||||
<el-input
|
||||
v-model="manageSearchQuery"
|
||||
placeholder="搜索应用..."
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
class="mb-4"
|
||||
>
|
||||
</el-input>
|
||||
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
<el-checkbox-group v-model="selectedApps">
|
||||
<el-checkbox
|
||||
v-for="app in filteredManageApps"
|
||||
:key="app.id"
|
||||
:label="app.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<el-avatar :src="app.icon" />
|
||||
<span class="text-sm">{{ app.name }}</span>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showManageMyApps = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveMyApps">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog
|
||||
title="确认删除"
|
||||
:model-value="showDeleteConfirm"
|
||||
width="30%"
|
||||
>
|
||||
<p>确定要从"我的应用"中移除 <span class="font-medium">{{ deletingAppName }}</span> 吗?</p>
|
||||
<template #footer>
|
||||
<el-button @click="showDeleteConfirm = false">取消</el-button>
|
||||
<el-button type="danger" @click="confirmDelete">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import {appCenterList} from '../api/appcenter'
|
||||
import draggable from 'vuedraggable'
|
||||
import {
|
||||
Collection,
|
||||
Search,
|
||||
Delete,
|
||||
Remove,
|
||||
Rank,
|
||||
// 应用图标
|
||||
Monitor,
|
||||
Document,
|
||||
Setting,
|
||||
User,
|
||||
Message,
|
||||
Calendar,
|
||||
PieChart,
|
||||
Download,
|
||||
Upload,
|
||||
Folder,
|
||||
Clock,
|
||||
Bell,
|
||||
Lock,
|
||||
Share,
|
||||
Star
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElDialog } from 'element-plus'
|
||||
|
||||
// 应用分类数据
|
||||
const categories = ref([
|
||||
])
|
||||
|
||||
// 应用类型定义
|
||||
interface App {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
icon: any
|
||||
category: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const drag=ref(false)
|
||||
|
||||
// 所有应用数据
|
||||
const allApps = ref<App[]>([
|
||||
|
||||
])
|
||||
|
||||
// 我的应用ID列表(从本地存储加载或设置默认值)
|
||||
const myAppIds = ref<number[]>([])
|
||||
|
||||
// 搜索和筛选相关状态
|
||||
const searchQuery = ref('')
|
||||
const activeCategory = ref('all')
|
||||
const showManageMyApps = ref(false)
|
||||
const manageSearchQuery = ref('')
|
||||
const selectedApps = ref<number[]>([])
|
||||
|
||||
// 删除确认相关状态
|
||||
const showDeleteConfirm = ref(false)
|
||||
const deletingAppId = ref<number | null>(null)
|
||||
const deletingAppName = ref('')
|
||||
|
||||
// 计算属性:我的应用列表
|
||||
const myApps = computed<App[]>({
|
||||
get() {
|
||||
// 保持与myAppIds相同的顺序
|
||||
return myAppIds.value.map(id => allApps.value.find(app => app.id === id)).filter(Boolean) as App[]
|
||||
},
|
||||
set(newValue) {
|
||||
// 当拖拽排序后更新myAppIds
|
||||
myAppIds.value = newValue.map(app => app.id)
|
||||
}
|
||||
})
|
||||
|
||||
// 计算属性:筛选后的我的应用
|
||||
const filteredMyApps = computed(() => {
|
||||
if (!searchQuery.value.trim()) return myApps.value
|
||||
|
||||
const query = searchQuery.value.toLowerCase().trim()
|
||||
return myApps.value.filter(app =>
|
||||
app.name.toLowerCase().includes(query) ||
|
||||
app.description.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
|
||||
// 计算属性:筛选后的所有应用
|
||||
const filteredAllApps = computed(() => {
|
||||
let apps = allApps.value
|
||||
|
||||
// 按分类筛选
|
||||
if (activeCategory.value !== 'all') {
|
||||
apps = apps.filter(app => app.category === activeCategory.value)
|
||||
}
|
||||
|
||||
// 按搜索词筛选
|
||||
if (searchQuery.value.trim()) {
|
||||
const query = searchQuery.value.toLowerCase().trim()
|
||||
apps = apps.filter(app =>
|
||||
app.name.toLowerCase().includes(query) ||
|
||||
app.description.toLowerCase().includes(query)
|
||||
)
|
||||
}
|
||||
|
||||
return apps
|
||||
})
|
||||
|
||||
// 计算属性:管理对话框中的应用列表
|
||||
const filteredManageApps = computed(() => {
|
||||
if (!manageSearchQuery.value.trim()) return allApps.value
|
||||
|
||||
const query = manageSearchQuery.value.toLowerCase().trim()
|
||||
return allApps.value.filter(app =>
|
||||
app.name.toLowerCase().includes(query) ||
|
||||
app.description.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
|
||||
// 检查应用是否在我的应用中
|
||||
const isAppInMyApps = (appId: number) => {
|
||||
return myAppIds.value.includes(appId)
|
||||
}
|
||||
|
||||
// 添加应用到我的应用
|
||||
const addToMyApps = (app: App) => {
|
||||
if (!isAppInMyApps(app.id)) {
|
||||
myAppIds.value.push(app.id)
|
||||
saveMyAppsToLocalStorage()
|
||||
|
||||
// 显示成功提示
|
||||
ElMessage({
|
||||
message: `已将"${app.name}"添加到我的应用`,
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 显示删除确认对话框
|
||||
const confirmRemoveFromMyApps = (app: App) => {
|
||||
console.log(app)
|
||||
deletingAppId.value = app.id
|
||||
deletingAppName.value = app.name
|
||||
showDeleteConfirm.value = true
|
||||
}
|
||||
|
||||
// 确认删除应用
|
||||
const confirmDelete = () => {
|
||||
if (deletingAppId.value) {
|
||||
myAppIds.value = myAppIds.value.filter(id => id !== deletingAppId.value)
|
||||
saveMyAppsToLocalStorage()
|
||||
|
||||
// 显示成功提示
|
||||
ElMessage({
|
||||
message: `已从我的应用中移除"${deletingAppName.value}"`,
|
||||
type: 'info'
|
||||
})
|
||||
|
||||
// 关闭对话框
|
||||
showDeleteConfirm.value = false
|
||||
deletingAppId.value = null
|
||||
deletingAppName.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 打开应用
|
||||
const openApp = (app: App) => {
|
||||
// 在实际应用中,这里可以实现跳转逻辑
|
||||
console.log(`Opening app: ${app.name} (${app.url})`)
|
||||
|
||||
// 显示打开提示
|
||||
ElMessage({
|
||||
message: `正在打开"${app.name}"`,
|
||||
type: 'info'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存我的应用设置
|
||||
const saveMyApps = () => {
|
||||
myAppIds.value = [...selectedApps.value]
|
||||
saveMyAppsToLocalStorage()
|
||||
showManageMyApps.value = false
|
||||
|
||||
// 显示保存成功提示
|
||||
ElMessage({
|
||||
message: '我的应用设置已保存',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭管理对话框前的处理
|
||||
const handleCloseManageMyApps = (done: () => void) => {
|
||||
// 恢复之前的选择
|
||||
selectedApps.value = [...myAppIds.value]
|
||||
done()
|
||||
}
|
||||
|
||||
// 拖拽结束事件处理
|
||||
const onDragEnd = () => {
|
||||
drag.value=false
|
||||
// 拖拽排序后保存
|
||||
saveMyAppsToLocalStorage()
|
||||
ElMessage({
|
||||
message: '应用顺序已更新',
|
||||
type: 'success',
|
||||
duration: 1000
|
||||
})
|
||||
}
|
||||
|
||||
// 将我的应用保存到本地存储
|
||||
const saveMyAppsToLocalStorage = () => {
|
||||
localStorage.setItem('myApps', JSON.stringify(myAppIds.value))
|
||||
}
|
||||
|
||||
// 从本地存储加载我的应用
|
||||
const loadMyAppsFromLocalStorage = () => {
|
||||
const savedMyApps = localStorage.getItem('myApps')
|
||||
if (savedMyApps) {
|
||||
try {
|
||||
myAppIds.value = JSON.parse(savedMyApps)
|
||||
} catch (e) {
|
||||
console.error('Failed to parse saved my apps', e)
|
||||
// 解析失败时使用默认值
|
||||
myAppIds.value = [1, 2, 3, 4]
|
||||
}
|
||||
} else {
|
||||
// 如果没有保存的我的应用,设置一些默认值
|
||||
myAppIds.value = [1, 2, 3, 4]
|
||||
}
|
||||
|
||||
// 初始化管理对话框中的选择
|
||||
selectedApps.value = [...myAppIds.value]
|
||||
}
|
||||
|
||||
|
||||
const loadAppList = () => {
|
||||
appCenterList().then(res=>{
|
||||
if (res.code===200){
|
||||
|
||||
categories.value = res.data.appGroup.map(item=>({
|
||||
id: item.ssoAppGroupId,
|
||||
name: item.name
|
||||
}))
|
||||
|
||||
allApps.value = res.data.appList.map(item=>({
|
||||
id: item.ssoAppId,
|
||||
name: item.appName,
|
||||
description: item.remark,
|
||||
icon: item.appIcon,
|
||||
category: item.ssoAppGroupId,
|
||||
url: item.url
|
||||
}))
|
||||
|
||||
|
||||
console.log(res)
|
||||
// allApps.value = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 生命周期钩子:组件挂载后加载我的应用
|
||||
onMounted(() => {
|
||||
loadAppList()
|
||||
loadMyAppsFromLocalStorage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-card {
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 拖拽相关样式 */
|
||||
.drag-handle {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
/* 隐藏拖拽过程中的默认高亮样式 */
|
||||
:deep(.ghost) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
:deep(.dragging) {
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@ -1,417 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="font-inter bg-gray-100 text-dark flex overflow-hidden h-full">
|
||||
<!-- 左侧聊天列表 -->
|
||||
<div class="w-80 bg-white border-r border-gray-200 flex flex-col h-full shadow-sm z-10">
|
||||
<!-- 头部 -->
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-xl font-bold text-primary">企业沟通</h1>
|
||||
<button class="p-2 rounded-full hover:bg-gray-100 transition-colors">
|
||||
<i class="fa fa-plus text-gray-600"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="relative">
|
||||
<input type="text" placeholder="搜索聊天..." class="w-full py-2 pl-10 pr-4 rounded-lg bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary/30 transition-all">
|
||||
<i class="fa fa-search absolute left-3 top-3 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天列表导航 -->
|
||||
<div class="flex border-b border-gray-200">
|
||||
<button class="flex-1 py-3 text-primary border-b-2 border-primary font-medium">
|
||||
最近聊天
|
||||
</button>
|
||||
<button class="flex-1 py-3 text-gray-500 hover:text-gray-700 transition-colors">
|
||||
群聊
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 聊天列表内容 -->
|
||||
<div class="flex-1 overflow-y-auto scrollbar-hide">
|
||||
<!-- 当前选中的聊天 -->
|
||||
<div class="flex items-center p-3 bg-primary/5 border-l-4 border-primary cursor-pointer">
|
||||
<img src="https://picsum.photos/id/1005/200/200" alt="研发部群聊" class="w-12 h-12 rounded-full object-cover">
|
||||
<div class="ml-3 flex-1 min-w-0">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-900 truncate">研发部群聊</h3>
|
||||
<span class="text-xs text-gray-500">14:32</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 truncate mt-1">
|
||||
<span class="font-medium">你:</span>这个需求我已经完成了
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未读消息聊天 -->
|
||||
<div class="flex items-center p-3 hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<img src="https://picsum.photos/id/1012/200/200" alt="张经理" class="w-12 h-12 rounded-full object-cover">
|
||||
<div class="ml-3 flex-1 min-w-0">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-900 truncate">张经理</h3>
|
||||
<span class="text-xs text-gray-500">13:45</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 truncate mt-1">
|
||||
下周的项目评审会议需要提前准备
|
||||
</p>
|
||||
<div class="absolute right-4 top-4 w-5 h-5 bg-primary rounded-full flex items-center justify-center text-white text-xs font-medium">
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 普通聊天 -->
|
||||
<div class="flex items-center p-3 hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<img src="https://picsum.photos/id/1027/200/200" alt="产品组" class="w-12 h-12 rounded-full object-cover">
|
||||
<div class="ml-3 flex-1 min-w-0">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-900 truncate">产品组</h3>
|
||||
<span class="text-xs text-gray-500">昨天</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 truncate mt-1">
|
||||
李华:新功能原型已经上传到共享文件夹
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-3 hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<img src="https://picsum.photos/id/1025/200/200" alt="王工程师" class="w-12 h-12 rounded-full object-cover">
|
||||
<div class="ml-3 flex-1 min-w-0">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-900 truncate">王工程师</h3>
|
||||
<span class="text-xs text-gray-500">昨天</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 truncate mt-1">
|
||||
数据库优化方案我已经发你邮箱了
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-3 hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<img src="https://picsum.photos/id/1066/200/200" alt="市场部" class="w-12 h-12 rounded-full object-cover">
|
||||
<div class="ml-3 flex-1 min-w-0">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-900 truncate">市场部</h3>
|
||||
<span class="text-xs text-gray-500">周一</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 truncate mt-1">
|
||||
张敏:下个月的市场活动计划已更新
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间聊天界面 -->
|
||||
<div class="flex-1 flex flex-col bg-gray-50 h-full overflow-hidden">
|
||||
<!-- 聊天头部 -->
|
||||
<div class="bg-white border-b border-gray-200 p-4 flex items-center justify-between shadow-sm">
|
||||
<div class="flex items-center">
|
||||
<img src="https://picsum.photos/id/1005/200/200" alt="研发部群聊" class="w-10 h-10 rounded-full object-cover">
|
||||
<div class="ml-3">
|
||||
<h2 class="font-semibold">研发部群聊</h2>
|
||||
<p class="text-xs text-gray-500">12名成员,8人在线</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<button class="p-2 rounded-full hover:bg-gray-100 transition-colors text-gray-600">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
<button class="p-2 rounded-full hover:bg-gray-100 transition-colors text-gray-600">
|
||||
<i class="fa fa-phone"></i>
|
||||
</button>
|
||||
<button class="p-2 rounded-full hover:bg-gray-100 transition-colors text-gray-600">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<button class="p-2 rounded-full hover:bg-gray-100 transition-colors text-gray-600">
|
||||
<i class="fa fa-ellipsis-v"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天消息区域 -->
|
||||
<div class="flex-1 overflow-y-auto p-6 space-y-6 scrollbar-hide" id="chat-messages">
|
||||
<!-- 日期分隔线 -->
|
||||
<div class="flex justify-center">
|
||||
<span class="text-xs bg-gray-200 text-gray-500 px-3 py-1 rounded-full">今天</span>
|
||||
</div>
|
||||
|
||||
<!-- 他人消息 -->
|
||||
<div class="flex items-start">
|
||||
<img src="https://picsum.photos/id/1025/200/200" alt="王工程师" class="w-8 h-8 rounded-full object-cover">
|
||||
<div class="ml-2 max-w-[80%]">
|
||||
<div class="flex items-center mb-1">
|
||||
<span class="text-xs font-medium text-gray-700">王工程师</span>
|
||||
<span class="text-xs text-gray-400 ml-2">09:32</span>
|
||||
</div>
|
||||
<div class="bg-white p-3 rounded-lg message-bubble-left shadow-sm">
|
||||
<p>大家上午好,昨天部署的新版本运行情况如何?有没有发现什么问题?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 他人消息 - 图片 -->
|
||||
<div class="flex items-start">
|
||||
<img src="https://picsum.photos/id/1012/200/200" alt="张经理" class="w-8 h-8 rounded-full object-cover">
|
||||
<div class="ml-2 max-w-[80%]">
|
||||
<div class="flex items-center mb-1">
|
||||
<span class="text-xs font-medium text-gray-700">张经理</span>
|
||||
<span class="text-xs text-gray-400 ml-2">10:15</span>
|
||||
</div>
|
||||
<div class="bg-white p-3 rounded-lg message-bubble-left shadow-sm">
|
||||
<p>我这边发现一个界面显示问题,主要在IE浏览器上:</p>
|
||||
<div class="mt-2 rounded overflow-hidden border border-gray-100">
|
||||
<img src="https://picsum.photos/id/0/400/200" alt="问题截图" class="w-full h-auto hover:opacity-90 transition-opacity cursor-pointer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 他人消息 - 文件 -->
|
||||
<div class="flex items-start">
|
||||
<img src="https://picsum.photos/id/1066/200/200" alt="李华" class="w-8 h-8 rounded-full object-cover">
|
||||
<div class="ml-2 max-w-[80%]">
|
||||
<div class="flex items-center mb-1">
|
||||
<span class="text-xs font-medium text-gray-700">李华</span>
|
||||
<span class="text-xs text-gray-400 ml-2">11:45</span>
|
||||
</div>
|
||||
<div class="bg-white p-3 rounded-lg message-bubble-left shadow-sm">
|
||||
<p>这是修复方案的文档,请查收:</p>
|
||||
<div class="mt-2 flex items-center p-2 bg-gray-50 rounded">
|
||||
<div class="w-10 h-10 bg-primary/10 rounded flex items-center justify-center text-primary">
|
||||
<i class="fa fa-file-pdf-o text-xl"></i>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm font-medium text-gray-800 truncate">前端兼容性修复方案.pdf</p>
|
||||
<p class="text-xs text-gray-500">2.4 MB</p>
|
||||
</div>
|
||||
<button class="text-primary hover:text-primary/80 transition-colors">
|
||||
<i class="fa fa-download"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自己的消息 -->
|
||||
<div class="flex items-start justify-end">
|
||||
<div class="mr-2 max-w-[80%]">
|
||||
<div class="flex items-center justify-end mb-1">
|
||||
<span class="text-xs text-gray-400">13:20</span>
|
||||
</div>
|
||||
<div class="bg-primary text-white p-3 rounded-lg message-bubble-right shadow-sm">
|
||||
<p>我看了一下,这个问题是由于IE对flex布局的支持不完善导致的,我会尽快修复。</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://picsum.photos/id/1001/200/200" alt="我" class="w-8 h-8 rounded-full object-cover">
|
||||
</div>
|
||||
|
||||
<!-- 自己的消息 - 表情包 -->
|
||||
<div class="flex items-start justify-end">
|
||||
<div class="mr-2 max-w-[80%]">
|
||||
<div class="flex items-center justify-end mb-1">
|
||||
<span class="text-xs text-gray-400">13:22</span>
|
||||
</div>
|
||||
<div class="bg-primary text-white p-3 rounded-lg message-bubble-right shadow-sm">
|
||||
<p>收到文档了,谢谢!👍👍</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://picsum.photos/id/1001/200/200" alt="我" class="w-8 h-8 rounded-full object-cover">
|
||||
</div>
|
||||
|
||||
<!-- 他人消息 -->
|
||||
<div class="flex items-start">
|
||||
<img src="https://picsum.photos/id/1025/200/200" alt="王工程师" class="w-8 h-8 rounded-full object-cover">
|
||||
<div class="ml-2 max-w-[80%]">
|
||||
<div class="flex items-center mb-1">
|
||||
<span class="text-xs font-medium text-gray-700">王工程师</span>
|
||||
<span class="text-xs text-gray-400 ml-2">14:30</span>
|
||||
</div>
|
||||
<div class="bg-white p-3 rounded-lg message-bubble-left shadow-sm">
|
||||
<p>数据库性能监控系统已经部署完成,大家可以通过内部地址访问查看实时数据。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自己的消息 -->
|
||||
<div class="flex items-start justify-end">
|
||||
<div class="mr-2 max-w-[80%]">
|
||||
<div class="flex items-center justify-end mb-1">
|
||||
<span class="text-xs text-gray-400">14:32</span>
|
||||
</div>
|
||||
<div class="bg-primary text-white p-3 rounded-lg message-bubble-right shadow-sm">
|
||||
<p>这个需求我已经完成了</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://picsum.photos/id/1001/200/200" alt="我" class="w-8 h-8 rounded-full object-cover">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="bg-white border-t border-gray-200 p-3">
|
||||
<div class="flex items-center mb-2 space-x-1">
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="表情">
|
||||
<i class="fa fa-smile-o text-lg"></i>
|
||||
</button>
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="图片">
|
||||
<i class="fa fa-picture-o text-lg"></i>
|
||||
</button>
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="文件">
|
||||
<i class="fa fa-paperclip text-lg"></i>
|
||||
</button>
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="视频">
|
||||
<i class="fa fa-video-camera text-lg"></i>
|
||||
</button>
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="截图">
|
||||
<i class="fa fa-desktop text-lg"></i>
|
||||
</button>
|
||||
<div class="flex-1"></div>
|
||||
<button class="p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="更多选项">
|
||||
<i class="fa fa-ellipsis-h text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<textarea placeholder="输入消息..." class="flex-1 border border-gray-200 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary p-3 resize-none transition-all" rows="3"></textarea>
|
||||
<button class="bg-primary hover:bg-primary/90 text-white px-6 rounded-r-lg transition-colors flex items-center">
|
||||
<span>发送</span>
|
||||
<i class="fa fa-paper-plane ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧在线人员 -->
|
||||
<div class="w-72 bg-white border-l border-gray-200 flex flex-col h-full shadow-sm hidden lg:block">
|
||||
<!-- 头部 -->
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<h2 class="font-semibold">在线成员 (8)</h2>
|
||||
</div>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<div class="p-3 border-b border-gray-200">
|
||||
<div class="relative">
|
||||
<input type="text" placeholder="搜索成员..." class="w-full py-2 pl-10 pr-4 rounded-lg bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary/30 transition-all text-sm">
|
||||
<i class="fa fa-search absolute left-3 top-3 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分类 -->
|
||||
<div class="p-2 border-b border-gray-200">
|
||||
<button class="text-sm text-primary font-medium">全部</button>
|
||||
<button class="text-sm text-gray-500 ml-3 hover:text-gray-700 transition-colors">部门</button>
|
||||
<button class="text-sm text-gray-500 ml-3 hover:text-gray-700 transition-colors">角色</button>
|
||||
</div>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<div class="flex-1 overflow-y-auto scrollbar-hide p-2">
|
||||
<!-- 自己 -->
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1001/200/200" alt="自己" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-success border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">我 (自己)</h3>
|
||||
<p class="text-xs text-gray-500">前端开发</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 在线成员 -->
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1012/200/200" alt="张经理" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-success border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">张经理</h3>
|
||||
<p class="text-xs text-gray-500">研发经理</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1025/200/200" alt="王工程师" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-success border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">王工程师</h3>
|
||||
<p class="text-xs text-gray-500">后端开发</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1066/200/200" alt="李华" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-success border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">李华</h3>
|
||||
<p class="text-xs text-gray-500">前端开发</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1027/200/200" alt="赵设计师" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-success border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">赵设计师</h3>
|
||||
<p class="text-xs text-gray-500">UI/UX设计</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 离开状态 -->
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1074/200/200" alt="陈测试" class="w-10 h-10 rounded-full object-cover">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-warning border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-900">陈测试</h3>
|
||||
<p class="text-xs text-gray-500">测试工程师</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 离线成员 -->
|
||||
<div class="mt-4">
|
||||
<h3 class="text-xs text-gray-500 uppercase font-medium px-2 mb-2">离线成员 (4)</h3>
|
||||
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1083/200/200" alt="孙产品" class="w-10 h-10 rounded-full object-cover opacity-70">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-gray-300 border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-600">孙产品</h3>
|
||||
<p class="text-xs text-gray-400">产品经理</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-2 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<div class="relative">
|
||||
<img src="https://picsum.photos/id/1076/200/200" alt="周运维" class="w-10 h-10 rounded-full object-cover opacity-70">
|
||||
<span class="absolute bottom-0 right-0 w-3 h-3 bg-gray-300 border-2 border-white rounded-full"></span>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-gray-600">周运维</h3>
|
||||
<p class="text-xs text-gray-400">运维工程师</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,70 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6">欢迎使用企业内网门户</h1>
|
||||
|
||||
<!-- 页面内容示例 -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-4">系统公告</h2>
|
||||
<el-alert
|
||||
title="系统升级通知"
|
||||
type="info"
|
||||
description="本系统将于本周六凌晨2点进行例行维护,预计维护时间为2小时。维护期间系统将暂停服务,请提前做好工作安排。"
|
||||
show-icon
|
||||
:closable="false"
|
||||
></el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片示例 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||||
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||
<div slot="header" class="flex justify-between items-center">
|
||||
<span>项目统计</span>
|
||||
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<el-progress :percentage="75" type="circle" :width="120"></el-progress>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<p class="text-gray-500 text-sm">进行中项目</p>
|
||||
<p class="text-blue-600 text-2xl font-bold">12/16</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||
<div slot="header" class="flex justify-between items-center">
|
||||
<span>待办事项</span>
|
||||
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<el-progress :percentage="40" type="circle" :width="120" color="#f56c6c"></el-progress>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<p class="text-gray-500 text-sm">今日待办</p>
|
||||
<p class="text-red-500 text-2xl font-bold">6/15</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||
<div slot="header" class="flex justify-between items-center">
|
||||
<span>通知消息</span>
|
||||
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||
</div>
|
||||
<div class="flex items-center justify-center">
|
||||
<el-progress :percentage="20" type="circle" :width="120" color="#e6a23c"></el-progress>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<p class="text-gray-500 text-sm">未读消息</p>
|
||||
<p class="text-orange-500 text-2xl font-bold">3/15</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Layout from "./menu/index.vue";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<layout></layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,128 +0,0 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import { useRoute } from 'vue-router'
|
||||
import {login as loginUrl} from "../data/host.js";
|
||||
import {login} from "../api/login.js";
|
||||
import userInfo from "../data/userInfo.js";
|
||||
const route = useRoute()
|
||||
|
||||
const loginState = ref(0)
|
||||
|
||||
const setBar=(progress)=>{
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
progressBar.style.width = `${progress}%`;
|
||||
}
|
||||
|
||||
const jumpLogin = () => {
|
||||
location.href=loginUrl
|
||||
}
|
||||
|
||||
const handlerLogin = () => {
|
||||
const code= route.query.code
|
||||
const state= route.query.state
|
||||
if (code&&state) {
|
||||
setBar(50)
|
||||
login({code,state}).then(res=>{
|
||||
console.log(res)
|
||||
if (res.code===200){
|
||||
setBar(100)
|
||||
loginState.value=1
|
||||
userInfo.setUserInfo(res.data)
|
||||
location.href="/"
|
||||
}else {
|
||||
setBar(0)
|
||||
loginState.value=2
|
||||
}
|
||||
})
|
||||
}else {
|
||||
jumpLogin()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
handlerLogin()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="font-inter bg-gray-50 min-h-screen flex items-center justify-center p-4">
|
||||
<div class="max-w-md w-full bg-white rounded-xl shadow-lg overflow-hidden animate-fade-in">
|
||||
<!-- 头部区域 -->
|
||||
<div class="bg-primary p-6 text-white text-center">
|
||||
<div class="flex items-center justify-center">
|
||||
<i class="fa fa-sign-in text-4xl mr-3"></i>
|
||||
<h1 class="text-2xl font-bold">企业登录系统</h1>
|
||||
</div>
|
||||
<p class="mt-2 text-primary-100 opacity-90">正在验证您的身份,请稍候...</p>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="p-8">
|
||||
<!-- 状态指示器 -->
|
||||
<div id="status-container" class="flex flex-col items-center">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loginState===0" class="animate-slide-up">
|
||||
<div class="w-16 h-16 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto"></div>
|
||||
<p class="mt-4 text-gray-600 text-center">正在验证授权码...</p>
|
||||
<p id="loading-details" class="mt-2 text-center text-sm text-gray-500">请不要关闭此页面</p>
|
||||
</div>
|
||||
|
||||
<!-- 成功状态 (默认隐藏) -->
|
||||
<div v-if="loginState===1" class=" animate-slide-up">
|
||||
<p class="mt-4 text-gray-600 text-center">验证成功</p>
|
||||
<p id="success-details" class="mt-2 text-sm text-center text-gray-500">正在跳转至应用...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 (默认隐藏) -->
|
||||
<div v-if="loginState===2" class=" animate-slide-up text-center">
|
||||
<p class="mt-4 text-gray-600 text-center">登录失败</p>
|
||||
<p id="error-details" class="mt-2 text-sm text-gray-500 text-center">授权码无效或已过期</p>
|
||||
<button @click="jumpLogin" class=" mt-6 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors">
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="mt-8">
|
||||
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div id="progress-bar" class="h-full bg-primary rounded-full w-0 transition-all duration-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部区域 -->
|
||||
<div class="p-4 bg-gray-50 text-center text-sm text-gray-500">
|
||||
<p>© 2025 企业内部系统 | 技术支持: it-support@company.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.animate-pulse-slow {
|
||||
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
.animate-slide-up {
|
||||
animation: slideUp 0.5s ease-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,230 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-screen overflow-hidden bg-gray-50">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="bg-primary text-white shadow-md h-16 flex items-center justify-between px-6">
|
||||
<!-- 左侧Logo和菜单按钮 -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex">
|
||||
<button @click="isCollapse = !isCollapse" class="mr-4 text-white focus:outline-none">
|
||||
<el-icon><Fold /></el-icon>
|
||||
</button>
|
||||
<div class="flex items-center">
|
||||
<el-icon><Platform /></el-icon>
|
||||
<span class="ml-2 text-lg font-semibold hidden md:block">企业内网门户</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-header">
|
||||
<el-menu
|
||||
mode="horizontal"
|
||||
:ellipsis="false"
|
||||
@open="handleOpen"
|
||||
router
|
||||
>
|
||||
<el-menu-item index="/news">新闻中心</el-menu-item>
|
||||
<el-menu-item index="/announcement">公告</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 右侧用户信息 -->
|
||||
<div class="flex items-center">
|
||||
<div class="relative mr-6">
|
||||
<button class="relative text-white focus:outline-none">
|
||||
<el-icon><Bell /></el-icon>
|
||||
<span class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">3</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<img :src="userInfoData?.avatar" alt="User Avatar" class="w-8 h-8 rounded-full object-cover">
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link text-white cursor-pointer ml-2">{{ userInfoData?.nickname }}</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="handelLogout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- 侧边栏菜单 -->
|
||||
<aside class="bg-white shadow-md z-10 transition-all duration-300" :style="{ width: isCollapse ? '64px' : '200px' }">
|
||||
<el-menu
|
||||
default-active="/home"
|
||||
:collapse="isCollapse"
|
||||
@open="handleOpen"
|
||||
@close="handleClose"
|
||||
router
|
||||
>
|
||||
<el-menu-item index="/home">
|
||||
<el-icon><IconMenu /></el-icon>
|
||||
<template #title>首页</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/app-center">
|
||||
<el-icon><Orange /></el-icon>
|
||||
<template #title>应用中心</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/staff-style">
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<template #title>员工风采</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/app-center">
|
||||
<el-icon><StarFilled /></el-icon>
|
||||
<template #title>企业论坛</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/chat">
|
||||
<el-icon><Comment /></el-icon>
|
||||
<template #title>企业聊天</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/suggestion-box">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
<template #title>意见箱</template>
|
||||
</el-menu-item>
|
||||
<!-- <el-sub-menu index="2">-->
|
||||
<!-- <template #title>-->
|
||||
<!-- <el-icon><Location /></el-icon>-->
|
||||
<!-- <span>导航一</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-menu-item-group>-->
|
||||
<!-- <template #title><span>分组一</span></template>-->
|
||||
<!-- <el-menu-item index="1-1">菜单项一</el-menu-item>-->
|
||||
<!-- <el-menu-item index="1-2">菜单项二</el-menu-item>-->
|
||||
<!-- </el-menu-item-group>-->
|
||||
<!-- <el-menu-item-group title="分组二">-->
|
||||
<!-- <el-menu-item index="1-3">菜单项三</el-menu-item>-->
|
||||
<!-- </el-menu-item-group>-->
|
||||
<!-- <el-sub-menu index="1-4">-->
|
||||
<!-- <template #title><span>菜单项四</span></template>-->
|
||||
<!-- <el-menu-item index="1-4-1">子菜单项一</el-menu-item>-->
|
||||
<!-- </el-sub-menu>-->
|
||||
<!-- </el-sub-menu>-->
|
||||
<!-- <el-menu-item index="3" disabled>-->
|
||||
<!-- <el-icon><Document /></el-icon>-->
|
||||
<!-- <template #title>导航三</template>-->
|
||||
<!-- </el-menu-item>-->
|
||||
<!-- <el-menu-item index="4">-->
|
||||
<!-- <el-icon><Setting /></el-icon>-->
|
||||
<!-- <template #title>导航四</template>-->
|
||||
<!-- </el-menu-item>-->
|
||||
</el-menu>
|
||||
</aside>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from 'vue'
|
||||
import {
|
||||
Document,
|
||||
Menu as IconMenu,
|
||||
Location,
|
||||
Setting,
|
||||
Switch,
|
||||
StarFilled,
|
||||
UserFilled,
|
||||
Comment,
|
||||
QuestionFilled,
|
||||
Orange,
|
||||
// 新增:导入需要使用的图标
|
||||
Fold,
|
||||
Platform,
|
||||
Bell,
|
||||
CaretBottom
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
|
||||
import userInfo from '../../data/userInfo.js'
|
||||
import {useRouter} from "vue-router";
|
||||
import { logout } from '../../api/login';
|
||||
const router=useRouter();
|
||||
const userInfoData=ref(null);
|
||||
|
||||
const isCollapse = ref(false)
|
||||
|
||||
const handleOpen = (key, keyPath) => {
|
||||
console.log(key, keyPath)
|
||||
}
|
||||
|
||||
const handleClose = (key, keyPath) => {
|
||||
console.log(key, keyPath)
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
const info=userInfo.getUserInfo()
|
||||
if (!info){
|
||||
router.push({path: '/login'})
|
||||
}else {
|
||||
userInfoData.value=info
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const handelLogout = () => {
|
||||
logout().then(res=>{
|
||||
if (res.code===200){
|
||||
userInfo.removeUserInfo()
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.top-header{
|
||||
border:none;
|
||||
:deep(.el-menu-item){
|
||||
background-color: #085ce6;
|
||||
color: #fff !important;
|
||||
border: none !important;
|
||||
}
|
||||
:deep(.el-menu-item):hover{
|
||||
color: #ffffff !important;
|
||||
background-color: #206ce8 !important;
|
||||
border: none !important;
|
||||
|
||||
|
||||
}
|
||||
:deep(.el-menu-item):active{
|
||||
color: #ffffff !important;
|
||||
background-color: #085ce6 !important;
|
||||
border: none !important;
|
||||
|
||||
|
||||
}
|
||||
|
||||
:deep(.el-menu-item):focus{
|
||||
color: #ffffff !important;
|
||||
background-color: #206ce8 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
margin-left: 20px;
|
||||
}
|
||||
:deep(.el-menu--horizontal.el-menu ){
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
@ -1,52 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElCard, ElTag } from 'element-plus';
|
||||
|
||||
// 模拟新闻数据,添加 imageUrl 字段
|
||||
const newsList = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '公司年度会议通知',
|
||||
content: '公司将于下周五召开年度会议,请各位员工提前做好准备。',
|
||||
date: '2025-07-24',
|
||||
imageUrl: 'https://picsum.photos/600/400?random=1'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '新产品上线公告',
|
||||
content: '我们的全新产品已正式上线,欢迎大家体验。',
|
||||
date: '2025-07-23',
|
||||
imageUrl: 'https://picsum.photos/600/400?random=2'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '团队建设活动安排',
|
||||
content: '本周末将组织团队建设活动,具体安排请查看内部通知。',
|
||||
date: '2025-07-22',
|
||||
imageUrl: 'https://picsum.photos/600/400?random=3'
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6 text-center">新闻列表</h1>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<ElCard v-for="news in newsList" :key="news.id" class="shadow-md hover:shadow-lg transition-shadow">
|
||||
<!-- 展示新闻图片 -->
|
||||
<img :src="news.imageUrl" alt="news image" class="w-full h-48 object-cover rounded-t-md">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl font-semibold text-gray-700">{{ news.title }}</span>
|
||||
<ElTag type="info" size="small">{{ news.date }}</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-gray-600 p-4">{{ news.content }}</p>
|
||||
</ElCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,58 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElCard, ElTag } from 'element-plus';
|
||||
|
||||
// 模拟员工数据
|
||||
const staffList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
position: '高级前端开发工程师',
|
||||
avatar: 'https://picsum.photos/200/200?random=1',
|
||||
intro: '张三在前端开发领域拥有丰富的经验,擅长 Vue、React 等主流框架,对前端性能优化有深入研究。',
|
||||
honors: ['优秀员工', '技术创新奖']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
position: '后端架构师',
|
||||
avatar: 'https://picsum.photos/200/200?random=2',
|
||||
intro: '李四专注于后端系统设计与开发,精通 Java、Go 语言,主导过多个大型项目的架构设计。',
|
||||
honors: ['杰出贡献奖', '最佳团队成员']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
position: '产品经理',
|
||||
avatar: 'https://picsum.photos/200/200?random=3',
|
||||
intro: '王五具备敏锐的市场洞察力和出色的产品规划能力,成功打造过多款用户喜爱的产品。',
|
||||
honors: ['优秀产品奖', '最佳创意奖']
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-8 text-center">员工风采</h1>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<ElCard v-for="staff in staffList" :key="staff.id" class="shadow-md hover:shadow-xl transition-shadow">
|
||||
<div class="flex flex-col items-center">
|
||||
<img :src="staff.avatar" alt="staff avatar" class="w-32 h-32 rounded-full object-cover mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-700">{{ staff.name }}</h2>
|
||||
<p class="text-gray-600 text-sm mb-4">{{ staff.position }}</p>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<p class="text-gray-600 mb-4">{{ staff.intro }}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ElTag v-for="honor in staff.honors" :key="honor" type="success" size="small">
|
||||
{{ honor }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,127 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 定义表单数据结构
|
||||
interface FeedbackForm {
|
||||
department: string;
|
||||
contact: string;
|
||||
phone: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const formData = ref<FeedbackForm>({
|
||||
department: '',
|
||||
contact: '',
|
||||
phone: '',
|
||||
content: ''
|
||||
});
|
||||
|
||||
// 提交方法(可根据实际需求对接接口)
|
||||
const handleSubmit = () => {
|
||||
console.log('提交的表单数据:', formData.value);
|
||||
// 这里可补充 axios 等请求库调用后端接口逻辑,示例仅打印
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="feedback-container">
|
||||
<h2 class="title">意见反馈</h2>
|
||||
<div class="form-wrapper">
|
||||
<div class="form-item">
|
||||
<label class="form-label">部门:</label>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="formData.department"
|
||||
placeholder="请输入部门名称"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">联系人:</label>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="formData.contact"
|
||||
placeholder="请输入联系人姓名"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">联系电话:</label>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="formData.phone"
|
||||
placeholder="请输入联系电话"
|
||||
type="tel"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label class="form-label">需求内容:</label>
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="formData.content"
|
||||
placeholder="请详细描述需求内容"
|
||||
></textarea>
|
||||
</div>
|
||||
<button class="submit-btn" @click="handleSubmit">提交意见</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.feedback-container {
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
margin-bottom: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
caret-color: white;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
align-self: flex-end;
|
||||
padding: 10px 20px;
|
||||
background-color: #0078d4;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(
|
||||
),
|
||||
],
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user