- 新增`.env.production`文件,配置生产环境API基础URL - 优化`vite.config.js`,支持环境变量和API代理配置 - 重构`request.js`,简化请求拦截器和响应处理逻辑 - 新增`cases.js`和`config.js`存储模块,管理案例和配置数据 - 更新`index.html`,修改页面标题为公司名称 - 优化`CaseDetail.vue`和`Team.vue`,使用新的存储模块和组件 - 新增`CaseItem.vue`和`TeamMemberCard.vue`组件,提升代码复用性 - 删除旧的`casesStore.js`和`caseList.vue`,统一使用新的存储和组件 - 更新`App.vue`,使用配置存储动态渲染导航栏和页脚内容hfll
| @ -1,5 +1,5 @@ | |||
| # 开发环境API基础URL | |||
| VITE_API_BASE_URL=http://localhost:3000 | |||
| # 其他开发环境变量 | |||
| # VITE_APP_MODE=development | |||
| # 开发环境 | |||
| VITE_APP_BASE_API = '/dev-api' | |||
| @ -0,0 +1,3 @@ | |||
| # 生产环境 | |||
| VITE_APP_BASE_API = '/prod-api' | |||
| @ -0,0 +1,158 @@ | |||
| -- 创建数据库 | |||
| CREATE DATABASE IF NOT EXISTS hanhai_website COMMENT='瀚海黎明官方网站数据库'; | |||
| USE hanhai_website; | |||
| -- 创建服务表:存储网站提供的服务信息 | |||
| CREATE TABLE services ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '服务ID,自增主键', | |||
| icon VARCHAR(255) NOT NULL COMMENT '服务图标URL', | |||
| title VARCHAR(100) NOT NULL COMMENT '服务标题', | |||
| description TEXT NOT NULL COMMENT '服务详细描述', | |||
| category VARCHAR(50) NULL COMMENT '服务分类', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='服务信息表'; | |||
| -- 创建案例表:存储公司完成的项目案例 | |||
| CREATE TABLE cases ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '案例ID,自增主键', | |||
| title VARCHAR(100) NOT NULL COMMENT '案例标题', | |||
| description TEXT NOT NULL COMMENT '案例简介', | |||
| image VARCHAR(255) NOT NULL COMMENT '案例主图URL', | |||
| category VARCHAR(50) NOT NULL COMMENT '案例分类', | |||
| client VARCHAR(100) NOT NULL COMMENT '客户名称', | |||
| completion_date VARCHAR(50) NOT NULL COMMENT '完成日期', | |||
| challenge TEXT NOT NULL COMMENT '客户面临的挑战', | |||
| solution TEXT NOT NULL COMMENT '提供的解决方案', | |||
| results TEXT NOT NULL COMMENT '项目成果', | |||
| testimonial TEXT NULL COMMENT '客户评价', | |||
| testimonial_author VARCHAR(100) NULL COMMENT '评价人及职位', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='项目案例表'; | |||
| -- 创建案例服务关联表:存储案例使用的服务列表 | |||
| CREATE TABLE case_services ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '关联ID,自增主键', | |||
| case_id INT NOT NULL COMMENT '关联的案例ID', | |||
| service_name VARCHAR(100) NOT NULL COMMENT '服务名称', | |||
| FOREIGN KEY (case_id) REFERENCES cases(id) ON DELETE CASCADE COMMENT '外键关联案例表,案例删除时级联删除' | |||
| ) COMMENT='案例服务关联表'; | |||
| -- 创建案例图库表:存储案例的多张展示图片 | |||
| CREATE TABLE case_gallery ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '图片ID,自增主键', | |||
| case_id INT NOT NULL COMMENT '关联的案例ID', | |||
| image_url VARCHAR(255) NOT NULL COMMENT '图片URL', | |||
| display_order INT NOT NULL DEFAULT 0 COMMENT '显示顺序', | |||
| FOREIGN KEY (case_id) REFERENCES cases(id) ON DELETE CASCADE COMMENT '外键关联案例表,案例删除时级联删除' | |||
| ) COMMENT='案例图库表'; | |||
| -- 创建团队成员表:存储公司团队成员信息 | |||
| CREATE TABLE team_members ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '成员ID,自增主键', | |||
| name VARCHAR(50) NOT NULL COMMENT '成员姓名', | |||
| position VARCHAR(100) NOT NULL COMMENT '职位', | |||
| bio TEXT NOT NULL COMMENT '个人简介', | |||
| photo VARCHAR(255) NOT NULL COMMENT '照片URL', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='团队成员表'; | |||
| -- 创建社交媒体表:存储团队成员的社交媒体链接 | |||
| CREATE TABLE social_media ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '社交媒体ID,自增主键', | |||
| member_id INT NOT NULL COMMENT '关联的成员ID', | |||
| type VARCHAR(50) NOT NULL COMMENT '社交媒体类型(如wechat, linkedin)', | |||
| url VARCHAR(255) NOT NULL COMMENT '社交媒体链接', | |||
| qrcode VARCHAR(255) NULL COMMENT '二维码图片URL(微信等)', | |||
| FOREIGN KEY (member_id) REFERENCES team_members(id) ON DELETE CASCADE COMMENT '外键关联成员表,成员删除时级联删除' | |||
| ) COMMENT='社交媒体表'; | |||
| -- 创建职位表:存储招聘职位信息 | |||
| CREATE TABLE job_openings ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '职位ID,自增主键', | |||
| title VARCHAR(100) NOT NULL COMMENT '职位名称', | |||
| description TEXT NOT NULL COMMENT '职位描述', | |||
| requirements TEXT NULL COMMENT '职位要求', | |||
| link VARCHAR(255) NOT NULL COMMENT '职位详情链接', | |||
| is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='招聘职位表'; | |||
| -- 创建组件表:存储网站可复用组件的配置 | |||
| CREATE TABLE components ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '组件ID,自增主键', | |||
| name VARCHAR(100) NOT NULL COMMENT '组件名称', | |||
| type VARCHAR(50) NOT NULL COMMENT '组件类型', | |||
| content JSON NOT NULL COMMENT '组件内容(JSON格式)', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='网站组件表'; | |||
| -- 创建用户表:存储后台管理用户 | |||
| CREATE TABLE users ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID,自增主键', | |||
| username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', | |||
| password VARCHAR(255) NOT NULL COMMENT '密码(加密存储)', | |||
| email VARCHAR(100) NOT NULL UNIQUE COMMENT '电子邮箱', | |||
| role VARCHAR(20) NOT NULL DEFAULT 'editor' COMMENT '用户角色(admin/editor)', | |||
| last_login TIMESTAMP NULL COMMENT '最后登录时间', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | |||
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' | |||
| ) COMMENT='后台用户表'; | |||
| -- 创建联系表单表:存储网站访客提交的联系信息 | |||
| CREATE TABLE contact_messages ( | |||
| id INT PRIMARY KEY AUTO_INCREMENT COMMENT '消息ID,自增主键', | |||
| name VARCHAR(100) NOT NULL COMMENT '联系人姓名', | |||
| email VARCHAR(100) NOT NULL COMMENT '联系人邮箱', | |||
| phone VARCHAR(20) NULL COMMENT '联系电话', | |||
| subject VARCHAR(200) NOT NULL COMMENT '主题', | |||
| message TEXT NOT NULL COMMENT '消息内容', | |||
| status VARCHAR(20) DEFAULT 'unread' COMMENT '消息状态(unread/read/replied)', | |||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' | |||
| ) COMMENT='联系表单消息表'; | |||
| -- 向服务表插入初始数据 | |||
| INSERT INTO services (icon, title, description) VALUES | |||
| ('https://cdn-icons-png.flaticon.com/512/2920/2920277.png', '定制软件开发', '根据您的业务需求,量身定制专属软件解决方案'), | |||
| ('https://cdn-icons-png.flaticon.com/512/2586/2586488.png', '移动应用开发', '打造高性能、用户友好的iOS和Android应用'), | |||
| ('https://cdn-icons-png.flaticon.com/512/1055/1055687.png', 'Web应用开发', '开发响应式、现代化的Web应用和网站'), | |||
| ('https://cdn-icons-png.flaticon.com/512/1935/1935765.png', '企业软件解决方案', '提供ERP、CRM等企业级软件解决方案'), | |||
| ('https://cdn-icons-png.flaticon.com/512/4727/4727266.png', '云服务与DevOps', '云架构设计、部署和DevOps自动化服务'), | |||
| ('https://cdn-icons-png.flaticon.com/512/1055/1055666.png', 'UI/UX设计', '创造直观、美观且用户友好的界面设计'); | |||
| -- 向案例表插入初始数据 | |||
| INSERT INTO cases (title, description, image, category, client, completion_date, challenge, solution, results, testimonial, testimonial_author) VALUES | |||
| ('智慧校园系统', '为教育机构打造的一体化校园管理系统,涵盖教学、行政、学生服务等多个模块', 'https://images.unsplash.com/photo-1523240795612-9a054b0db644?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', '企业系统', '某知名高校', '2023年8月', '客户面临的挑战是多个独立系统并行运行,数据不一致,管理效率低下。学生和教职工需要在多个系统间切换,用户体验不佳。', '我们为客户设计并实现了一套集成化的智慧校园系统,整合了教务管理、学生服务、行政办公、资源管理等多个模块。系统采用了统一的用户界面和数据标准,实现了单点登录和数据共享。我们还开发了移动端应用,方便师生随时随地访问系统功能。', '系统上线后,管理效率提升了50%,数据处理错误减少了80%,用户满意度提高了60%。系统的自助服务功能减轻了行政人员的工作负担,让他们能够专注于更有价值的任务。', '微隐软件工作室的团队展现了卓越的项目管理和技术能力。他们深入理解了我们复杂的业务需求,并提供了一套既全面又易用的解决方案。新系统极大地改善了我们的管理效率和服务质量。', '陈校长 - 客户负责人'); | |||
| -- 向案例服务关联表插入数据 | |||
| INSERT INTO case_services (case_id, service_name) VALUES | |||
| (1, '系统规划'), | |||
| (1, '软件开发'), | |||
| (1, '数据迁移'), | |||
| (1, '用户培训'), | |||
| (1, '持续支持'); | |||
| -- 向案例图库表插入数据 | |||
| INSERT INTO case_gallery (case_id, image_url, display_order) VALUES | |||
| (1, 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', 1), | |||
| (1, 'https://images.unsplash.com/photo-1562774053-701939374585?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', 2), | |||
| (1, 'https://images.unsplash.com/photo-1577896851231-70ef18881754?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', 3); | |||
| -- 向团队成员表插入初始数据 | |||
| INSERT INTO team_members (name, position, bio, photo) VALUES | |||
| ('张明', '创始人 & CEO', '拥有15年软件开发和团队管理经验,曾在多家知名科技公司担任技术负责人', 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80'), | |||
| ('李婷', '技术总监', '计算机科学博士,专注于人工智能和大数据领域,拥有多项技术专利', 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80'); | |||
| -- 向社交媒体表插入数据 | |||
| INSERT INTO social_media (member_id, type, url, qrcode) VALUES | |||
| (1, 'linkedin', 'https://linkedin.com/', NULL), | |||
| (1, 'github', 'https://github.com/', NULL), | |||
| (1, 'wechat', 'javascript:void(0);', '/images/qrcode-wechat.jpg'), | |||
| (1, 'wecom', 'javascript:void(0);', '/images/qrcode-wecom.jpg'), | |||
| (2, 'linkedin', 'https://linkedin.com/', NULL), | |||
| (2, 'github', 'https://github.com/', NULL), | |||
| (2, 'xiaohongshu', 'https://xiaohongshu.com/', NULL); | |||
| @ -1,13 +1,16 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| <head> | |||
| <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> | |||
| <title>湖南瀚海黎明信息科技有限公司</title> | |||
| </head> | |||
| <body> | |||
| <div id="app"></div> | |||
| <script type="module" src="/src/main.js"></script> | |||
| </body> | |||
| </html> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,16 @@ | |||
| import { get, post, put, del } from '../utils/request' | |||
| // API接口管理 | |||
| const api = { | |||
| // 获取配置数据 | |||
| fetchConfigParams: () => get('/api/officialWebsite/configParams/list'), | |||
| getCategoryList: () => get('/api/officialWebsite/caseCategory/list'), | |||
| getCasesList: (params) => get('/api/officialWebsite/cases/list', params), | |||
| addLeaveMessage: (params) => post('/api/officialWebsite/leaveMessage', params), | |||
| getSelectedCase: () => get('/api/officialWebsite/cases/selected'), | |||
| getDevelopmentHistory: () => get('/api/officialWebsite/developmentHistory/list'), | |||
| getServiceProcess: () => get('/api/officialWebsite/serviceProcess/list') | |||
| } | |||
| export default api | |||
| @ -0,0 +1,251 @@ | |||
| <script setup> | |||
| // 接收props参数 | |||
| const props = defineProps({ | |||
| // 标题文本 | |||
| title: { | |||
| type: String, | |||
| default: '准备好开始您的项目了吗?' | |||
| }, | |||
| // 描述文本 | |||
| description: { | |||
| type: String, | |||
| default: '我们的团队随时准备为您提供专业的技术支持和服务' | |||
| }, | |||
| // 主按钮文本 | |||
| primaryButtonText: { | |||
| type: String, | |||
| default: '立即咨询' | |||
| }, | |||
| // 主按钮链接 | |||
| primaryButtonLink: { | |||
| type: String, | |||
| default: '/contact' | |||
| }, | |||
| // 次按钮文本 | |||
| secondaryButtonText: { | |||
| type: String, | |||
| default: '电话联系' | |||
| }, | |||
| // 次按钮链接 | |||
| secondaryButtonLink: { | |||
| type: String, | |||
| default: 'tel:+8612345678901' | |||
| }, | |||
| // 是否显示图标 | |||
| showIcon: { | |||
| type: Boolean, | |||
| default: true | |||
| }, | |||
| // 图标类名 | |||
| iconClass: { | |||
| type: String, | |||
| default: 'fas fa-rocket' | |||
| } | |||
| }); | |||
| </script> | |||
| <template> | |||
| <section class="cta-section" data-aos="fade-up"> | |||
| <div class="container"> | |||
| <div class="cta-content"> | |||
| <div v-if="showIcon" class="cta-icon" data-aos="zoom-in" data-aos-delay="200"> | |||
| <i :class="iconClass"></i> | |||
| </div> | |||
| <h2 data-aos="fade-up" data-aos-delay="300">{{ title }}</h2> | |||
| <p data-aos="fade-up" data-aos-delay="400">{{ description }}</p> | |||
| <div class="cta-buttons" data-aos="fade-up" data-aos-delay="500"> | |||
| <a :href="primaryButtonLink" class="btn-primary"> | |||
| {{ primaryButtonText }} | |||
| <i class="fas fa-arrow-right"></i> | |||
| </a> | |||
| <a :href="secondaryButtonLink" class="btn-outline">{{ secondaryButtonText }}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| .cta-section { | |||
| background: linear-gradient(135deg, #0056b3, #00337f); | |||
| background-size: 200% 200%; | |||
| color: white; | |||
| padding: 80px 0; | |||
| text-align: center; | |||
| margin-top: 40px; | |||
| position: relative; | |||
| overflow: hidden; | |||
| box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.1); | |||
| animation: gradientBG 15s ease infinite; | |||
| } | |||
| @keyframes gradientBG { | |||
| 0% { | |||
| background-position: 0% 50%; | |||
| } | |||
| 50% { | |||
| background-position: 100% 50%; | |||
| } | |||
| 100% { | |||
| background-position: 0% 50%; | |||
| } | |||
| } | |||
| .cta-section::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 0; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| background-image: url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"%3E%3Cpath fill="%23ffffff" fill-opacity="0.05" d="M0,96L48,112C96,128,192,160,288,186.7C384,213,480,235,576,213.3C672,192,768,128,864,128C960,128,1056,192,1152,213.3C1248,235,1344,213,1392,202.7L1440,192L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"%3E%3C/path%3E%3C/svg%3E'); | |||
| background-size: cover; | |||
| background-position: center; | |||
| opacity: 0.1; | |||
| z-index: 0; | |||
| } | |||
| .container { | |||
| width: 100%; | |||
| max-width: 1200px; | |||
| margin: 0 auto; | |||
| padding: 0 15px; | |||
| } | |||
| .cta-content { | |||
| position: relative; | |||
| z-index: 1; | |||
| } | |||
| .cta-icon { | |||
| margin-bottom: 25px; | |||
| i { | |||
| font-size: 3.5rem; | |||
| color: #fff; | |||
| background: rgba(255, 255, 255, 0.1); | |||
| width: 100px; | |||
| height: 100px; | |||
| line-height: 100px; | |||
| border-radius: 50%; | |||
| display: inline-block; | |||
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |||
| transition: all 0.3s ease; | |||
| &:hover { | |||
| transform: translateY(-5px) scale(1.05); | |||
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2); | |||
| background: rgba(255, 255, 255, 0.2); | |||
| } | |||
| } | |||
| } | |||
| .cta-content { | |||
| h2 { | |||
| font-size: 2.5rem; | |||
| margin-bottom: 20px; | |||
| font-weight: 700; | |||
| text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); | |||
| color: #fff; | |||
| } | |||
| p { | |||
| font-size: 1.2rem; | |||
| opacity: 0.9; | |||
| max-width: 600px; | |||
| margin: 0 auto 30px; | |||
| line-height: 1.6; | |||
| } | |||
| } | |||
| .cta-buttons { | |||
| display: flex; | |||
| justify-content: center; | |||
| gap: 20px; | |||
| margin-top: 30px; | |||
| .btn-primary { | |||
| padding: 14px 32px; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| background-color: #3498db; | |||
| color: white; | |||
| border: 2px solid #3498db; | |||
| border-radius: 30px; | |||
| text-decoration: none; | |||
| transition: all 0.3s ease; | |||
| box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); | |||
| i { | |||
| margin-left: 8px; | |||
| transition: transform 0.3s ease; | |||
| } | |||
| &:hover { | |||
| background-color: #2980b9; | |||
| border-color: #2980b9; | |||
| transform: translateY(-3px); | |||
| i { | |||
| transform: translateX(5px); | |||
| } | |||
| } | |||
| } | |||
| .btn-outline { | |||
| background: transparent; | |||
| color: white; | |||
| border: 2px solid rgba(255, 255, 255, 0.6); | |||
| padding: 14px 32px; | |||
| font-size: 1.1rem; | |||
| font-weight: 600; | |||
| border-radius: 30px; | |||
| text-decoration: none; | |||
| transition: all 0.3s ease; | |||
| &:hover { | |||
| background: rgba(255, 255, 255, 0.1); | |||
| border-color: white; | |||
| transform: translateY(-3px); | |||
| box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1); | |||
| } | |||
| } | |||
| } | |||
| /* 响应式样式 */ | |||
| @media (max-width: 768px) { | |||
| .cta-content { | |||
| h2 { | |||
| font-size: 2rem; | |||
| } | |||
| p { | |||
| font-size: 1.1rem; | |||
| padding: 0 15px; | |||
| } | |||
| } | |||
| .cta-buttons { | |||
| flex-direction: column; | |||
| align-items: center; | |||
| width: 100%; | |||
| gap: 15px; | |||
| .btn-primary, | |||
| .btn-outline { | |||
| width: 80%; | |||
| max-width: 300px; | |||
| text-align: center; | |||
| } | |||
| } | |||
| .cta-icon { | |||
| i { | |||
| width: 80px; | |||
| height: 80px; | |||
| line-height: 80px; | |||
| font-size: 2.8rem; | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @ -0,0 +1,207 @@ | |||
| <script setup> | |||
| // 接收团队成员数据和配置选项作为props | |||
| const props = defineProps({ | |||
| // 成员数据 | |||
| member: { | |||
| type: Object, | |||
| required: true | |||
| }, | |||
| // 是否启用悬停效果 | |||
| enableHover: { | |||
| type: Boolean, | |||
| default: true | |||
| }, | |||
| // 图片高度 | |||
| imageHeight: { | |||
| type: String, | |||
| default: '300px' | |||
| }, | |||
| // 社交媒体类型映射(用于支持小红书、企业微信等中国平台) | |||
| socialMapping: { | |||
| type: Object, | |||
| default: () => ({ | |||
| // 国际平台 | |||
| linkedin: { icon: 'fab fa-linkedin', label: 'LinkedIn' }, | |||
| github: { icon: 'fab fa-github', label: 'GitHub' }, | |||
| twitter: { icon: 'fab fa-twitter', label: '推特' }, | |||
| dribbble: { icon: 'fab fa-dribbble', label: 'Dribbble' }, | |||
| behance: { icon: 'fab fa-behance', label: 'Behance' }, | |||
| // 中国平台 | |||
| wechat: { icon: 'fab fa-weixin', label: '微信' }, | |||
| wecom: { icon: 'fab fa-weixin', label: '企业微信' }, | |||
| xiaohongshu: { icon: 'fas fa-book', label: '小红书' }, | |||
| zhihu: { icon: 'fas fa-z', label: '知乎' }, | |||
| weibo: { icon: 'fab fa-weibo', label: '微博' }, | |||
| douyin: { icon: 'fab fa-tiktok', label: '抖音' } | |||
| }) | |||
| } | |||
| }); | |||
| // 点击联系方式事件 | |||
| const emit = defineEmits(['contact']); | |||
| // 联系团队成员 | |||
| const contactMember = (type) => { | |||
| emit('contact', { | |||
| memberId: props.member.id, | |||
| name: props.member.name, | |||
| contactType: type | |||
| }); | |||
| }; | |||
| </script> | |||
| <template> | |||
| <div class="member-card" :class="{ 'hover-enabled': enableHover }" data-aos="fade-up"> | |||
| <div class="member-photo" :style="{ height: imageHeight }"> | |||
| <img :src="member.photo" :alt="member.name"> | |||
| <div class="floating-socials"> | |||
| <a v-for="(social, index) in member.social" :key="index" :href="social.url" | |||
| :title="socialMapping[social.type]?.label || social.type" target="_blank" rel="noopener noreferrer" | |||
| @click.prevent="contactMember(social.type)"> | |||
| <i :class="socialMapping[social.type]?.icon || `fab fa-${social.type}`"></i> | |||
| </a> | |||
| </div> | |||
| </div> | |||
| <div class="member-info"> | |||
| <h3>{{ member.name }}</h3> | |||
| <p class="position">{{ member.position }}</p> | |||
| <p class="bio">{{ member.bio }}</p> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| /* 导入全局SCSS变量 */ | |||
| @use '../assets/scss/main.scss' as *; | |||
| .member-card { | |||
| background: white; | |||
| border-radius: 8px; | |||
| overflow: hidden; | |||
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |||
| &.hover-enabled { | |||
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||
| .member-photo img { | |||
| transform: scale(1.05); | |||
| } | |||
| } | |||
| } | |||
| .member-photo { | |||
| position: relative; | |||
| overflow: hidden; | |||
| &:before { | |||
| content: ''; | |||
| position: absolute; | |||
| bottom: 0; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 50%; | |||
| background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); | |||
| z-index: 1; | |||
| } | |||
| img { | |||
| width: 100%; | |||
| height: 100%; | |||
| object-fit: cover; | |||
| transition: transform 0.5s ease; | |||
| } | |||
| .floating-socials { | |||
| position: absolute; | |||
| bottom: 15px; | |||
| right: 15px; | |||
| display: flex; | |||
| gap: 10px; | |||
| z-index: 2; | |||
| a { | |||
| width: 36px; | |||
| height: 36px; | |||
| border-radius: 50%; | |||
| background-color: rgba(255, 255, 255, 0.9); | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| transition: all 0.3s ease; | |||
| i { | |||
| color: $primary-color; | |||
| font-size: 18px; | |||
| } | |||
| &:hover { | |||
| background-color: $primary-color; | |||
| transform: translateY(-3px); | |||
| i { | |||
| color: white; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .member-info { | |||
| padding: 20px; | |||
| h3 { | |||
| font-size: 1.4rem; | |||
| margin-bottom: 5px; | |||
| color: $text-color; | |||
| } | |||
| .position { | |||
| color: $primary-color; | |||
| font-weight: 500; | |||
| margin-bottom: 10px; | |||
| font-size: 1.1rem; | |||
| } | |||
| .bio { | |||
| color: $text-light; | |||
| font-size: 0.95rem; | |||
| line-height: 1.5; | |||
| margin-bottom: 15px; | |||
| } | |||
| } | |||
| } | |||
| /* 响应式处理 */ | |||
| @media (max-width: 768px) { | |||
| .member-card { | |||
| .member-info { | |||
| h3 { | |||
| font-size: 1.2rem; | |||
| } | |||
| .position { | |||
| font-size: 0.9rem; | |||
| } | |||
| .bio { | |||
| font-size: 0.9rem; | |||
| } | |||
| } | |||
| .member-photo .floating-socials a { | |||
| width: 32px; | |||
| height: 32px; | |||
| i { | |||
| font-size: 16px; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @ -1,167 +0,0 @@ | |||
| <script setup> | |||
| import { ref, onMounted, computed } from 'vue'; | |||
| import { useRoute, useRouter } from 'vue-router'; | |||
| // 路由实例 | |||
| const route = useRoute(); | |||
| const router = useRouter(); | |||
| // 查看相关案例 | |||
| const viewRelatedCase = (id) => { | |||
| router.push(`/cases/${id}`); | |||
| }; | |||
| // 定义组件属性 | |||
| const props = defineProps({ | |||
| cases: { | |||
| type: Array, | |||
| required: true | |||
| }, | |||
| }); | |||
| </script> | |||
| <template> | |||
| <!-- 相关案例 --> | |||
| <div class="related-grid"> | |||
| <div v-for="caseItem in cases" :key="caseItem.id" class="related-case-card" data-aos="fade-up" | |||
| @click="viewRelatedCase(caseItem.id)"> | |||
| <div class="case-image"> | |||
| <img :src="caseItem.image" :alt="caseItem.title"> | |||
| <div class="case-overlay"> | |||
| <span class="view-more">查看详情 <i class="fas fa-arrow-right"></i></span> | |||
| </div> | |||
| </div> | |||
| <div class="case-content"> | |||
| <div class="case-category">{{ caseItem.category }}</div> | |||
| <h3>{{ caseItem.title }}</h3> | |||
| <p>{{ caseItem.description }}</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| /* 导入全局SCSS变量 */ | |||
| @use '../../assets/scss/main.scss' as *; | |||
| .related-grid { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); | |||
| gap: 30px; | |||
| @media (max-width: 768px) { | |||
| grid-template-columns: 1fr; | |||
| } | |||
| } | |||
| .related-case-card { | |||
| background: white; | |||
| border-radius: 10px; | |||
| overflow: hidden; | |||
| box-shadow: $shadow-sm; | |||
| transition: all 0.3s ease; | |||
| cursor: pointer; | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: column; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: $shadow-lg; | |||
| .case-image img { | |||
| transform: scale(1.05); | |||
| } | |||
| .case-overlay { | |||
| opacity: 1; | |||
| } | |||
| .view-more { | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| } | |||
| .case-image { | |||
| position: relative; | |||
| height: 220px; | |||
| overflow: hidden; | |||
| img { | |||
| width: 100%; | |||
| height: 100%; | |||
| object-fit: cover; | |||
| transition: transform 0.5s ease; | |||
| } | |||
| .case-overlay { | |||
| position: absolute; | |||
| top: 0; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 100%; | |||
| background: rgba($primary-color, 0.7); | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| opacity: 0; | |||
| transition: opacity 0.3s ease; | |||
| } | |||
| .view-more { | |||
| color: white; | |||
| font-weight: 500; | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| transform: translateY(20px); | |||
| transition: transform 0.3s ease 0.1s; | |||
| i { | |||
| transition: transform 0.2s ease; | |||
| } | |||
| &:hover i { | |||
| transform: translateX(5px); | |||
| } | |||
| } | |||
| } | |||
| .case-content { | |||
| padding: 25px; | |||
| flex-grow: 1; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .case-category { | |||
| display: inline-block; | |||
| padding: 5px 12px; | |||
| background-color: rgba($primary-color, 0.1); | |||
| color: $primary-color; | |||
| border-radius: 20px; | |||
| font-size: 0.8rem; | |||
| font-weight: 500; | |||
| margin-bottom: 15px; | |||
| } | |||
| .case-content h3 { | |||
| font-size: 1.4rem; | |||
| color: $text-color; | |||
| margin-bottom: 10px; | |||
| transition: color 0.3s ease; | |||
| } | |||
| .case-content p { | |||
| color: $text-light; | |||
| font-size: 0.95rem; | |||
| line-height: 1.6; | |||
| margin-bottom: 20px; | |||
| flex-grow: 1; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,219 @@ | |||
| <script setup> | |||
| // 接收案例项数据作为props | |||
| const props = defineProps({ | |||
| item: { | |||
| type: Object, | |||
| required: true | |||
| }, | |||
| // 是否显示分类标签 | |||
| showCategory: { | |||
| type: Boolean, | |||
| default: true | |||
| }, | |||
| // 按钮类型:"link"链接样式 或 "button"按钮样式 | |||
| buttonType: { | |||
| type: String, | |||
| default: 'link' // 'link' 或 'button' | |||
| } | |||
| }); | |||
| // 点击查看详情事件 | |||
| const emit = defineEmits(['view-details']); | |||
| const viewDetails = () => { | |||
| emit('view-details', props.item.id); | |||
| }; | |||
| </script> | |||
| <template> | |||
| <div class="case-card" data-aos="fade-up" :data-aos-delay="item.delay"> | |||
| <div class="case-image"> | |||
| <div v-if="showCategory" class="category-tag">{{ item.categoryName }}</div> | |||
| <img :src="item.imageUrl && item.imageUrl.split(',')[0]" :alt="item.title" /> | |||
| </div> | |||
| <div class="case-content"> | |||
| <h3>{{ item.title }}</h3> | |||
| <p>{{ item.description }}</p> | |||
| <!-- 根据buttonType渲染不同的按钮样式 --> | |||
| <a v-if="buttonType === 'link'" href="#" class="btn-link" @click.prevent="viewDetails">查看详情</a> | |||
| <button v-else class="btn-details" @click="viewDetails">查看详情</button> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| /* 导入全局SCSS变量 */ | |||
| @use '../../assets/scss/main.scss' as *; | |||
| // .case-card { | |||
| // background: white; | |||
| // border-radius: 8px; | |||
| // overflow: hidden; | |||
| // box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |||
| // transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| // &:hover { | |||
| // transform: translateY(-10px); | |||
| // box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||
| // .case-image img { | |||
| // transform: scale(1.05); | |||
| // } | |||
| // } | |||
| // .case-image { | |||
| // position: relative; | |||
| // img { | |||
| // width: 100%; | |||
| // height: 300px; | |||
| // object-fit: cover; | |||
| // transition: transform 0.5s ease; | |||
| // } | |||
| // .category-tag { | |||
| // position: absolute; | |||
| // top: 15px; | |||
| // right: 15px; | |||
| // background-color: rgba(52, 152, 219, 0.9); | |||
| // color: white; | |||
| // padding: 6px 12px; | |||
| // border-radius: 4px; | |||
| // font-size: 0.8rem; | |||
| // font-weight: 600; | |||
| // z-index: 2; | |||
| // } | |||
| // } | |||
| // .case-content { | |||
| // padding: 25px; | |||
| // h3 { | |||
| // font-size: 1.5rem; | |||
| // margin-bottom: 15px; | |||
| // color: #2c3e50; | |||
| // } | |||
| // p { | |||
| // color: #7f8c8d; | |||
| // margin-bottom: 20px; | |||
| // } | |||
| // } | |||
| // } | |||
| // .btn-link { | |||
| // color: #3498db; | |||
| // text-decoration: none; | |||
| // font-weight: 600; | |||
| // position: relative; | |||
| // cursor: pointer; | |||
| // &:after { | |||
| // content: ''; | |||
| // position: absolute; | |||
| // width: 0; | |||
| // height: 2px; | |||
| // bottom: -5px; | |||
| // left: 0; | |||
| // background-color: #3498db; | |||
| // transition: width 0.3s ease; | |||
| // } | |||
| // &:hover { | |||
| // &:after { | |||
| // width: 100%; | |||
| // } | |||
| // } | |||
| // } | |||
| // .btn-details { | |||
| // background-color: #3498db; | |||
| // color: white; | |||
| // border: none; | |||
| // padding: 10px 20px; | |||
| // border-radius: 4px; | |||
| // font-weight: 600; | |||
| // cursor: pointer; | |||
| // transition: background-color 0.3s ease; | |||
| // &:hover { | |||
| // background-color: #2980b9; | |||
| // } | |||
| // } | |||
| .case-card { | |||
| border-radius: 12px; | |||
| overflow: hidden; | |||
| box-shadow: $shadow-sm; | |||
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| background: white; | |||
| position: relative; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: $shadow-lg; | |||
| .case-image img { | |||
| transform: scale(1.05); | |||
| } | |||
| } | |||
| .case-image { | |||
| position: relative; | |||
| overflow: hidden; | |||
| img { | |||
| width: 100%; | |||
| height: 250px; | |||
| object-fit: cover; | |||
| transition: transform 0.5s ease; | |||
| } | |||
| } | |||
| .category-tag { | |||
| position: absolute; | |||
| top: 15px; | |||
| right: 15px; | |||
| background: rgba($primary-color, 0.8); | |||
| color: white; | |||
| padding: 5px 12px; | |||
| border-radius: 20px; | |||
| font-size: 12px; | |||
| font-weight: 500; | |||
| z-index: 1; | |||
| } | |||
| .case-content { | |||
| padding: 25px; | |||
| background: white; | |||
| h3 { | |||
| font-size: 1.3rem; | |||
| margin-bottom: 10px; | |||
| color: $text-color; | |||
| } | |||
| p { | |||
| color: $text-light; | |||
| margin-bottom: 15px; | |||
| line-height: 1.6; | |||
| } | |||
| } | |||
| } | |||
| .btn-details { | |||
| display: inline-block; | |||
| padding: 8px 20px; | |||
| background: transparent; | |||
| border: 1px solid $primary-color; | |||
| color: $primary-color; | |||
| border-radius: 30px; | |||
| font-weight: 500; | |||
| cursor: pointer; | |||
| transition: all 0.3s ease; | |||
| &:hover { | |||
| background: $primary-color; | |||
| color: white; | |||
| } | |||
| } | |||
| </style> | |||
| @ -0,0 +1,74 @@ | |||
| <script setup> | |||
| // 导入案例项组件 | |||
| import CaseItem from '../cases/CaseItem.vue'; | |||
| import { useRouter } from 'vue-router'; | |||
| import { useCasesStore } from '@/stores/cases' | |||
| const { selectedCase } = useCasesStore() | |||
| // 获取路由实例 | |||
| const router = useRouter(); | |||
| // 查看案例详情处理函数 | |||
| const viewCaseDetails = (id) => { | |||
| router.push(`/cases/${id}`); | |||
| }; | |||
| </script> | |||
| <template> | |||
| <section class="cases-section" data-aos="fade-up"> | |||
| <div class="section-header"> | |||
| <h2>精选案例</h2> | |||
| <p>我们的成功项目展示</p> | |||
| </div> | |||
| <div class="cases-grid"> | |||
| <CaseItem | |||
| v-for="item in selectedCase" | |||
| :key="item.id" | |||
| :item="item" | |||
| buttonType="link" | |||
| @view-details="viewCaseDetails" | |||
| /> | |||
| </div> | |||
| </section> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| /* 全局样式 */ | |||
| .section-header { | |||
| text-align: center; | |||
| margin-bottom: 50px; | |||
| h2 { | |||
| font-size: 2.5rem; | |||
| color: #2c3e50; | |||
| margin-bottom: 15px; | |||
| } | |||
| p { | |||
| font-size: 1.2rem; | |||
| color: #7f8c8d; | |||
| } | |||
| } | |||
| .cases-section { | |||
| padding: 100px 20px; | |||
| background-color: #f8f9fa; | |||
| .cases-grid { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(300px, 3fr)); | |||
| gap: 30px; | |||
| margin-top: 40px; | |||
| max-width: 1200px; | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| } | |||
| } | |||
| @media (max-width: 768px) { | |||
| .cases-grid { | |||
| grid-template-columns: 2fr; | |||
| } | |||
| } | |||
| </style> | |||
| @ -1,137 +1,132 @@ | |||
| <script setup> | |||
| // 定义服务数据 | |||
| const services = [ | |||
| { | |||
| id: 1, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/2920/2920277.png', | |||
| title: '定制软件开发', | |||
| description: '根据您的业务需求,量身定制专属软件解决方案', | |||
| delay: 100 | |||
| }, | |||
| { | |||
| id: 2, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/2586/2586488.png', | |||
| title: '移动应用开发', | |||
| description: '打造高性能、用户友好的iOS和Android应用', | |||
| delay: 200 | |||
| }, | |||
| { | |||
| id: 3, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1055/1055687.png', | |||
| title: 'Web应用开发', | |||
| description: '开发响应式、现代化的Web应用和网站', | |||
| delay: 300 | |||
| }, | |||
| { | |||
| id: 4, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1935/1935765.png', | |||
| title: '企业软件解决方案', | |||
| description: '提供ERP、CRM等企业级软件解决方案', | |||
| delay: 400 | |||
| }, | |||
| { | |||
| id: 5, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/4727/4727266.png', | |||
| title: '云服务与DevOps', | |||
| description: '云架构设计、部署和DevOps自动化服务', | |||
| delay: 500 | |||
| }, | |||
| { | |||
| id: 6, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1055/1055666.png', | |||
| title: 'UI/UX设计', | |||
| description: '创造直观、美观且用户友好的界面设计', | |||
| delay: 600 | |||
| } | |||
| { | |||
| id: 1, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/2920/2920277.png', | |||
| title: '定制软件开发', | |||
| description: '根据您的业务需求,量身定制专属软件解决方案', | |||
| delay: 100 | |||
| }, | |||
| { | |||
| id: 2, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/2586/2586488.png', | |||
| title: '移动应用开发', | |||
| description: '打造高性能、用户友好的iOS和Android应用', | |||
| delay: 200 | |||
| }, | |||
| { | |||
| id: 3, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1055/1055687.png', | |||
| title: 'Web应用开发', | |||
| description: '开发响应式、现代化的Web应用和网站', | |||
| delay: 300 | |||
| }, | |||
| { | |||
| id: 4, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1935/1935765.png', | |||
| title: '企业软件解决方案', | |||
| description: '提供ERP、CRM等企业级软件解决方案', | |||
| delay: 400 | |||
| }, | |||
| { | |||
| id: 5, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/4727/4727266.png', | |||
| title: '云服务与DevOps', | |||
| description: '云架构设计、部署和DevOps自动化服务', | |||
| delay: 500 | |||
| }, | |||
| { | |||
| id: 6, | |||
| icon: 'https://cdn-icons-png.flaticon.com/512/1055/1055666.png', | |||
| title: 'UI/UX设计', | |||
| description: '创造直观、美观且用户友好的界面设计', | |||
| delay: 600 | |||
| } | |||
| ]; | |||
| </script> | |||
| <template> | |||
| <section class="services-section" data-aos="fade-up"> | |||
| <div class="section-header"> | |||
| <h2>我们的服务</h2> | |||
| <p>为您的业务提供全方位的软件解决方案</p> | |||
| </div> | |||
| <div class="services-grid"> | |||
| <div | |||
| v-for="service in services" | |||
| :key="service.id" | |||
| class="service-card" | |||
| data-aos="fade-up" | |||
| :data-aos-delay="service.delay" | |||
| > | |||
| <div class="service-icon"> | |||
| <img :src="service.icon" :alt="service.title" /> | |||
| <section class="services-section" data-aos="fade-up"> | |||
| <div class="section-header"> | |||
| <h2>我们的服务</h2> | |||
| <p>为您的业务提供全方位的软件解决方案</p> | |||
| </div> | |||
| <div class="services-grid"> | |||
| <div v-for="service in services" :key="service.id" class="service-card" data-aos="fade-up" | |||
| :data-aos-delay="service.delay"> | |||
| <div class="service-icon"> | |||
| <img :src="service.icon" :alt="service.title" /> | |||
| </div> | |||
| <h3>{{ service.title }}</h3> | |||
| <p>{{ service.description }}</p> | |||
| </div> | |||
| </div> | |||
| <h3>{{ service.title }}</h3> | |||
| <p>{{ service.description }}</p> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </section> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| .services-section { | |||
| padding: 100px 20px; | |||
| background-color: #f8f9fa; | |||
| .section-header { | |||
| text-align: center; | |||
| margin-bottom: 50px; | |||
| h2 { | |||
| font-size: 2.5rem; | |||
| color: #2c3e50; | |||
| margin-bottom: 15px; | |||
| } | |||
| p { | |||
| font-size: 1.2rem; | |||
| color: #7f8c8d; | |||
| padding: 100px 20px; | |||
| background-color: #f8f9fa; | |||
| .section-header { | |||
| text-align: center; | |||
| margin-bottom: 50px; | |||
| h2 { | |||
| font-size: 2.5rem; | |||
| color: #2c3e50; | |||
| margin-bottom: 15px; | |||
| } | |||
| p { | |||
| font-size: 1.2rem; | |||
| color: #7f8c8d; | |||
| } | |||
| } | |||
| } | |||
| .services-grid { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |||
| gap: 30px; | |||
| margin-top: 40px; | |||
| max-width: 1200px; | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| .service-card { | |||
| background: white; | |||
| border-radius: 8px; | |||
| padding: 30px; | |||
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |||
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| text-align: center; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||
| } | |||
| .service-icon { | |||
| margin-bottom: 20px; | |||
| img { | |||
| width: 80px; | |||
| height: 80px; | |||
| .services-grid { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |||
| gap: 30px; | |||
| margin-top: 40px; | |||
| max-width: 1200px; | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| .service-card { | |||
| background: white; | |||
| border-radius: 8px; | |||
| padding: 30px; | |||
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |||
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| text-align: center; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |||
| } | |||
| .service-icon { | |||
| margin-bottom: 20px; | |||
| img { | |||
| width: 80px; | |||
| height: 80px; | |||
| } | |||
| } | |||
| h3 { | |||
| font-size: 1.5rem; | |||
| margin-bottom: 15px; | |||
| color: #2c3e50; | |||
| } | |||
| p { | |||
| color: #7f8c8d; | |||
| } | |||
| } | |||
| } | |||
| h3 { | |||
| font-size: 1.5rem; | |||
| margin-bottom: 15px; | |||
| color: #2c3e50; | |||
| } | |||
| p { | |||
| color: #7f8c8d; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @ -0,0 +1,19 @@ | |||
| import { defineStore } from 'pinia' | |||
| import { ref, computed, reactive } from 'vue' | |||
| import api from '@/api' | |||
| // 定义案例数据存储 | |||
| export const useCasesStore = defineStore('cases', () => { | |||
| const selectedCase = ref([]) | |||
| const getSelectedCase = async () => { | |||
| const res = await api.getSelectedCase() | |||
| selectedCase.value = res.data | |||
| } | |||
| return { | |||
| selectedCase, | |||
| getSelectedCase, | |||
| } | |||
| }) | |||
| @ -1,150 +0,0 @@ | |||
| import { defineStore } from 'pinia' | |||
| import { ref, computed } from 'vue' | |||
| // 定义案例数据存储 | |||
| export const useCasesStore = defineStore('cases', () => { | |||
| // 案例数据 | |||
| const casesList = ref([ | |||
| { | |||
| id: 1, | |||
| title: '企业资源管理系统', | |||
| description: '为某制造企业开发的一套完整ERP系统,实现了生产、销售、库存等全流程管理', | |||
| image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: '企业系统', | |||
| client: '某制造业集团', | |||
| completionDate: '2023年6月', | |||
| services: ['系统架构设计', '数据库优化', '前端开发', '后端开发', '系统集成'], | |||
| challenge: '客户面临的主要挑战是多个业务系统之间数据孤岛问题,导致信息流通不畅,管理效率低下。同时,随着业务规模扩大,原有系统已无法满足日益增长的业务需求。', | |||
| solution: '我们为客户设计并实现了一套全面的企业资源管理系统,整合了生产、销售、采购、库存、财务等多个模块。系统采用微服务架构,确保了各模块之间的松耦合与高内聚,同时保证了系统的可扩展性和稳定性。我们还为客户定制了数据分析和决策支持功能,帮助管理层快速获取业务洞察。', | |||
| results: '系统上线后,客户的运营效率提升了35%,信息处理时间缩短了60%,库存周转率提高了25%,大幅降低了运营成本。同时,实时的数据分析功能帮助客户更快速地响应市场变化,提升了企业的竞争力。', | |||
| testimonial: '微隐软件工作室的团队展现了卓越的专业能力和服务态度。他们不仅理解我们的业务需求,还能提供创新的技术解决方案。新系统极大地提升了我们的运营效率,是我们数字化转型的重要一步。', | |||
| testimonialAuthor: '张总 - 客户CIO', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| }, | |||
| { | |||
| id: 2, | |||
| title: '电商平台重构', | |||
| description: '帮助某电商企业重构其线上平台,提升用户体验和系统性能,实现销售额提升30%', | |||
| image: 'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: 'Web应用', | |||
| client: '某知名电商企业', | |||
| completionDate: '2023年9月', | |||
| services: ['用户体验设计', '前端重构', '后端优化', '性能调优', '安全加固'], | |||
| challenge: '客户的电商平台面临用户体验不佳、系统响应缓慢、移动端适配不足等问题,导致用户流失和转化率下降。同时,随着业务量增长,系统稳定性也面临挑战。', | |||
| solution: '我们对客户的电商平台进行了全面重构,采用了现代化的前端框架和响应式设计,优化了用户界面和交互流程。在后端,我们重构了核心服务,引入了微服务架构和缓存机制,提升了系统性能和可扩展性。同时,我们还加强了系统的安全性,实现了全面的HTTPS和数据加密。', | |||
| results: '重构后的平台页面加载速度提升了65%,用户停留时间增加了40%,转化率提高了25%,最终带来了30%的销售额增长。系统的稳定性也得到了显著提升,即使在促销高峰期也能保持良好的性能。', | |||
| testimonial: '微隐软件工作室的团队展现了卓越的技术实力和创新思维。他们不仅解决了我们平台的技术问题,还从用户体验和业务角度提供了宝贵建议。重构后的平台获得了用户的一致好评,为我们带来了实质性的业务增长。', | |||
| testimonialAuthor: '李总 - 客户产品总监', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1556740758-90de374c12ad?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1556740772-1a741d976155?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1556761175-129418cb2dfe?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| }, | |||
| { | |||
| id: 3, | |||
| title: '医疗服务APP', | |||
| description: '为连锁医疗机构开发的患者服务APP,实现在线挂号、问诊和健康管理等功能', | |||
| image: 'https://images.unsplash.com/photo-1576091160550-2173dba999ef?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: '移动应用', | |||
| client: '某连锁医疗机构', | |||
| completionDate: '2023年12月', | |||
| services: ['移动应用开发', 'UI/UX设计', '后端API开发', '数据安全', '系统集成'], | |||
| challenge: '客户希望通过数字化手段提升患者服务体验,减少排队等待时间,同时提高医疗资源利用效率。传统的线下预约和就诊流程繁琐,患者满意度不高。', | |||
| solution: '我们为客户开发了一款功能全面的医疗服务APP,支持在线挂号、远程问诊、检查报告查询、健康档案管理等功能。APP采用了直观的用户界面和流畅的交互设计,确保各年龄段用户都能轻松使用。在技术层面,我们实现了与医院HIS系统的无缝集成,并采用了严格的数据加密和隐私保护措施。', | |||
| results: 'APP上线后,患者预约效率提升了80%,平均等待时间减少了65%,患者满意度提高了45%。同时,医生的工作效率也得到了提升,资源利用率增加了30%。APP已成为客户数字化医疗服务的核心平台。', | |||
| testimonial: '微隐软件工作室深入理解了医疗行业的特殊需求,为我们打造了一款既专业又易用的医疗服务APP。他们在确保数据安全和系统稳定性方面表现出色,为我们的患者提供了便捷、高效的服务体验。', | |||
| testimonialAuthor: '王院长 - 客户医疗总监', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1579684385127-1ef15d508118?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1583324113626-70df0f4deaab?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| }, | |||
| { | |||
| id: 4, | |||
| title: '金融数据可视化平台', | |||
| description: '为金融机构开发的数据分析和可视化平台,帮助决策者快速洞察市场趋势', | |||
| image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: 'Web应用', | |||
| client: '某投资管理公司', | |||
| completionDate: '2024年2月', | |||
| services: ['数据分析', '可视化设计', '前端开发', 'API集成', '实时数据处理'], | |||
| challenge: '客户需要一个能够整合多源金融数据,并提供直观、实时的可视化分析工具,帮助投资分析师和决策者快速识别市场趋势和投资机会。', | |||
| solution: '我们为客户开发了一个功能强大的金融数据可视化平台,整合了市场数据、公司财报、宏观经济指标等多种数据源。平台提供了丰富的图表类型和分析工具,支持自定义仪表盘和报告。我们采用了高性能的前端渲染技术和实时数据处理架构,确保了大数据量下的流畅体验。', | |||
| results: '平台上线后,客户的数据分析效率提升了70%,决策周期缩短了50%,投资组合表现超过了基准指数15%。平台的预测模型准确率达到了85%,为客户提供了显著的竞争优势。', | |||
| testimonial: '微隐软件工作室交付的数据可视化平台超出了我们的预期。他们不仅具备出色的技术能力,还展现了对金融行业的深刻理解。平台直观的界面和强大的分析功能极大地提升了我们的决策效率和准确性。', | |||
| testimonialAuthor: '赵总 - 客户投资总监', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1590283603385-17ffb3a7f29f?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| }, | |||
| { | |||
| id: 5, | |||
| title: '智慧校园系统', | |||
| description: '为教育机构打造的一体化校园管理系统,涵盖教学、行政、学生服务等多个模块', | |||
| image: 'https://images.unsplash.com/photo-1523240795612-9a054b0db644?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: '企业系统', | |||
| client: '某知名高校', | |||
| completionDate: '2023年8月', | |||
| services: ['系统规划', '软件开发', '数据迁移', '用户培训', '持续支持'], | |||
| challenge: '客户面临的挑战是多个独立系统并行运行,数据不一致,管理效率低下。学生和教职工需要在多个系统间切换,用户体验不佳。', | |||
| solution: '我们为客户设计并实现了一套集成化的智慧校园系统,整合了教务管理、学生服务、行政办公、资源管理等多个模块。系统采用了统一的用户界面和数据标准,实现了单点登录和数据共享。我们还开发了移动端应用,方便师生随时随地访问系统功能。', | |||
| results: '系统上线后,管理效率提升了50%,数据处理错误减少了80%,用户满意度提高了60%。系统的自助服务功能减轻了行政人员的工作负担,让他们能够专注于更有价值的任务。', | |||
| testimonial: '微隐软件工作室的团队展现了卓越的项目管理和技术能力。他们深入理解了我们复杂的业务需求,并提供了一套既全面又易用的解决方案。新系统极大地改善了我们的管理效率和服务质量。', | |||
| testimonialAuthor: '陈校长 - 客户负责人', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1562774053-701939374585?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1577896851231-70ef18881754?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| }, | |||
| { | |||
| id: 6, | |||
| title: '品牌官网设计', | |||
| description: '为高端品牌设计的响应式官方网站,展现品牌形象并提升用户转化率', | |||
| image: 'https://images.unsplash.com/photo-1561070791-2526d30994b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| category: 'UI/UX设计', | |||
| client: '某高端消费品牌', | |||
| completionDate: '2024年1月', | |||
| services: ['品牌策略', 'UI/UX设计', '前端开发', 'CMS实现', 'SEO优化'], | |||
| challenge: '客户的原有网站设计过时,无法有效展现品牌高端形象,且缺乏移动端适配,导致用户体验不佳和转化率低下。', | |||
| solution: '我们为客户重新设计了品牌官网,采用了现代简约的设计风格,突出品牌的高端定位。网站采用了响应式设计,确保在各种设备上都能提供出色的用户体验。我们还优化了产品展示和购买流程,降低了用户转化的摩擦。在技术层面,我们实现了高性能的前端架构和易用的内容管理系统。', | |||
| results: '新网站上线后,用户停留时间增加了45%,页面跳出率降低了30%,转化率提升了35%。网站的加载速度提高了60%,搜索引擎排名也有显著提升。客户的品牌形象得到了有效提升,线上销售额增长了40%。', | |||
| testimonial: '微隐软件工作室的设计团队展现了卓越的创意和专业能力。他们不仅理解我们的品牌价值,还能将其完美地转化为视觉设计。新网站获得了客户和合作伙伴的一致好评,成为了我们品牌传播的重要窗口。', | |||
| testimonialAuthor: '林总 - 客户市场总监', | |||
| gallery: [ | |||
| 'https://images.unsplash.com/photo-1542744094-3a31f272c490?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1542744173-8659239e9452?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80', | |||
| 'https://images.unsplash.com/photo-1542744173-05336fcc7ad4?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80' | |||
| ] | |||
| } | |||
| ]) | |||
| // 获取单个案例的方法 | |||
| const getCaseById = (id) => { | |||
| return casesList.value.find(item => item.id === id) || null | |||
| } | |||
| // 获取相关案例的方法 | |||
| const getRelatedCases = (currentCaseId, category) => { | |||
| return casesList.value | |||
| .filter(item => | |||
| item.id !== currentCaseId && | |||
| item.category === category | |||
| ) | |||
| .slice(0, 3) | |||
| } | |||
| return { | |||
| casesList, | |||
| getCaseById, | |||
| getRelatedCases | |||
| } | |||
| }) | |||
| @ -0,0 +1,26 @@ | |||
| import { defineStore } from 'pinia' | |||
| import { ref, computed, reactive } from 'vue' | |||
| import api from '@/api' | |||
| // 定义案例数据存储 | |||
| export const useConfigStore = defineStore('config', () => { | |||
| const configParams = reactive({}) | |||
| const map = { | |||
| 0 : 'text', | |||
| 1 : 'content', | |||
| 2 : 'imageUrl', | |||
| } | |||
| const getConfigParams = async () => { | |||
| const res = await api.fetchConfigParams() | |||
| res.forEach(item => { | |||
| configParams[item.code] = item[map[item.type]] | |||
| }) | |||
| } | |||
| return { | |||
| configParams, | |||
| getConfigParams | |||
| } | |||
| }) | |||
| @ -1,84 +0,0 @@ | |||
| # API请求工具使用说明 | |||
| ## 简介 | |||
| 本项目使用Axios封装了HTTP请求,并集中管理API接口,便于维护和使用。 | |||
| ## 文件结构 | |||
| - `request.js`: Axios实例配置和请求/响应拦截器 | |||
| - `api.js`: 集中管理所有API接口 | |||
| ## 使用方法 | |||
| ### 在组件中使用 | |||
| ```javascript | |||
| <script setup> | |||
| import { ref, onMounted } from 'vue' | |||
| import api from '@/utils/api' | |||
| // 定义响应式数据 | |||
| const homeData = ref(null) | |||
| const loading = ref(false) | |||
| const error = ref(null) | |||
| // 获取首页数据 | |||
| const fetchHomeData = async () => { | |||
| loading.value = true | |||
| error.value = null | |||
| try { | |||
| // 调用API | |||
| const res = await api.getHomeData() | |||
| homeData.value = res.data | |||
| } catch (err) { | |||
| error.value = err.message || '获取数据失败' | |||
| console.error('获取首页数据失败:', err) | |||
| } finally { | |||
| loading.value = false | |||
| } | |||
| } | |||
| // 页面加载时获取数据 | |||
| onMounted(() => { | |||
| fetchHomeData() | |||
| }) | |||
| </script> | |||
| ``` | |||
| ### 添加新的API接口 | |||
| 在`api.js`文件中添加新的接口定义: | |||
| ```javascript | |||
| // 在api对象中添加新接口 | |||
| const api = { | |||
| // 现有接口... | |||
| // 新增接口 | |||
| getNewData: (params) => get('/new-endpoint', params), | |||
| submitNewForm: (data) => post('/new-form', data), | |||
| } | |||
| ``` | |||
| ### 自定义请求配置 | |||
| 所有请求方法都支持传入自定义配置: | |||
| ```javascript | |||
| // 带自定义配置的请求 | |||
| api.getCustomData = (params) => get('/custom', params, { | |||
| timeout: 5000, | |||
| headers: { | |||
| 'Custom-Header': 'value' | |||
| } | |||
| }) | |||
| ``` | |||
| ## 注意事项 | |||
| 1. API基础URL通过环境变量`VITE_API_BASE_URL`配置 | |||
| 2. 默认已配置请求超时时间为15秒 | |||
| 3. 默认已配置token认证,会自动从localStorage获取token并添加到请求头 | |||
| 4. 响应拦截器会自动处理常见的HTTP错误状态码 | |||
| @ -1,46 +0,0 @@ | |||
| import { get, post, put, del } from './request' | |||
| // API接口管理 | |||
| const api = { | |||
| // 示例接口 | |||
| // 获取首页数据 | |||
| getHomeData: () => get('/home'), | |||
| // 获取关于我们数据 | |||
| getAboutData: () => get('/about'), | |||
| // 获取服务列表 | |||
| getServices: () => get('/services'), | |||
| // 获取案例列表 | |||
| getCases: (params) => get('/cases', params), | |||
| // 获取案例详情 | |||
| getCaseDetail: (id) => get(`/cases/${id}`), | |||
| // 获取团队成员 | |||
| getTeamMembers: () => get('/team'), | |||
| // 获取博客列表 | |||
| getBlogs: (params) => get('/blogs', params), | |||
| // 获取博客详情 | |||
| getBlogDetail: (id) => get(`/blogs/${id}`), | |||
| // 提交联系表单 | |||
| submitContact: (data) => post('/contact', data), | |||
| // 用户相关接口 | |||
| user: { | |||
| // 登录 | |||
| login: (data) => post('/user/login', data), | |||
| // 注册 | |||
| register: (data) => post('/user/register', data), | |||
| // 获取用户信息 | |||
| getInfo: () => get('/user/info'), | |||
| // 更新用户信息 | |||
| updateInfo: (data) => put('/user/info', data) | |||
| } | |||
| } | |||
| export default api | |||
| @ -1,112 +1,56 @@ | |||
| import axios from 'axios' | |||
| import axios from 'axios'; | |||
| // 创建axios实例 | |||
| const service = axios.create({ | |||
| // 设置基础URL,如果有环境变量可以使用环境变量 | |||
| baseURL: import.meta.env.VITE_API_BASE_URL || '', | |||
| // 请求超时时间 | |||
| timeout: 15000, | |||
| // 请求头设置 | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=utf-8' | |||
| } | |||
| }) | |||
| const api = axios.create({ | |||
| baseURL: import.meta.env.VITE_APP_BASE_API, | |||
| timeout: 50000, | |||
| headers: { | |||
| 'Content-Type': 'application/json' | |||
| } | |||
| }); | |||
| // 请求拦截器 | |||
| service.interceptors.request.use( | |||
| config => { | |||
| // 在发送请求之前做些什么 | |||
| // 例如:可以在这里统一添加token | |||
| const token = localStorage.getItem('token') | |||
| if (token) { | |||
| config.headers['Authorization'] = `Bearer ${token}` | |||
| api.interceptors.request.use( | |||
| config => { | |||
| const token = localStorage.getItem('token'); | |||
| if (token) { | |||
| config.headers.Authorization = `Bearer ${token}`; | |||
| } | |||
| return config; | |||
| }, | |||
| error => { | |||
| return Promise.reject(error); | |||
| } | |||
| return config | |||
| }, | |||
| error => { | |||
| // 对请求错误做些什么 | |||
| console.error('请求错误:', error) | |||
| return Promise.reject(error) | |||
| } | |||
| ) | |||
| ); | |||
| // 响应拦截器 | |||
| service.interceptors.response.use( | |||
| response => { | |||
| // 对响应数据做点什么 | |||
| const res = response.data | |||
| // 根据自己的业务逻辑判断响应是否成功 | |||
| // 这里假设后端返回的数据格式为 { code: 200, data: {}, message: '' } | |||
| if (res.code && res.code !== 200) { | |||
| // 处理业务错误 | |||
| console.error('业务错误:', res.message || '未知错误') | |||
| return Promise.reject(new Error(res.message || '未知错误')) | |||
| } | |||
| return res | |||
| }, | |||
| error => { | |||
| // 对响应错误做点什么 | |||
| let message = '网络错误' | |||
| if (error.response) { | |||
| // 请求已发出,但服务器响应的状态码不在 2xx 范围内 | |||
| switch (error.response.status) { | |||
| case 401: | |||
| message = '未授权,请重新登录' | |||
| // 可以在这里处理登出逻辑 | |||
| break | |||
| case 403: | |||
| message = '拒绝访问' | |||
| break | |||
| case 404: | |||
| message = '请求的资源不存在' | |||
| break | |||
| case 500: | |||
| message = '服务器内部错误' | |||
| break | |||
| default: | |||
| message = `请求错误: ${error.response.status}` | |||
| } | |||
| } else if (error.request) { | |||
| // 请求已发出,但没有收到响应 | |||
| message = '服务器未响应' | |||
| } else { | |||
| // 请求配置有误 | |||
| message = error.message | |||
| api.interceptors.response.use( | |||
| response => { | |||
| return response.data; | |||
| }, | |||
| error => { | |||
| if (error.response && error.response.status === 401) { | |||
| // 处理未授权的情况 | |||
| // 可以在这里触发退出登录或跳转到登录页 | |||
| } | |||
| return Promise.reject(error); | |||
| } | |||
| console.error('响应错误:', message) | |||
| return Promise.reject(error) | |||
| } | |||
| ) | |||
| ); | |||
| // 封装GET请求 | |||
| export function get(url, params, config = {}) { | |||
| return service.get(url, { | |||
| params, | |||
| ...config | |||
| }) | |||
| } | |||
| // 封装HTTP请求方法 | |||
| export const get = (url, params) => { | |||
| return api.get(url, { params }); | |||
| }; | |||
| // 封装POST请求 | |||
| export function post(url, data, config = {}) { | |||
| return service.post(url, data, config) | |||
| } | |||
| export const post = (url, data) => { | |||
| return api.post(url, data); | |||
| }; | |||
| // 封装PUT请求 | |||
| export function put(url, data, config = {}) { | |||
| return service.put(url, data, config) | |||
| } | |||
| export const put = (url, data) => { | |||
| return api.put(url, data); | |||
| }; | |||
| // 封装DELETE请求 | |||
| export function del(url, params, config = {}) { | |||
| return service.delete(url, { | |||
| params, | |||
| ...config | |||
| }) | |||
| } | |||
| export const del = (url) => { | |||
| return api.delete(url); | |||
| }; | |||
| // 导出axios实例 | |||
| export default service | |||
| export default api; | |||
| @ -1,141 +1,628 @@ | |||
| <script setup> | |||
| import { onMounted } from 'vue'; | |||
| import { onMounted, ref, watch } from 'vue'; | |||
| import PageHeader from '../../components/PageHeader.vue'; | |||
| onMounted(() => { | |||
| // 初始化代码将在组件挂载后执行 | |||
| import { useConfigStore } from '@/stores/config' | |||
| const { configParams } = useConfigStore() | |||
| import api from '@/api' | |||
| const developmentHistory = ref([]) | |||
| const getDevelopmentHistory = async () => { | |||
| const res = await api.getDevelopmentHistory() | |||
| developmentHistory.value = res.data | |||
| } | |||
| const activeTab = ref('profile'); | |||
| const isLoading = ref(false); | |||
| const historyLoaded = ref(false); | |||
| // 监听标签切换 | |||
| watch(activeTab, async (newTab) => { | |||
| if (newTab === 'history' && !historyLoaded.value) { | |||
| await loadHistoryData(); | |||
| } | |||
| }); | |||
| // 加载历史数据 | |||
| const loadHistoryData = async () => { | |||
| try { | |||
| isLoading.value = true; | |||
| await getDevelopmentHistory(); | |||
| historyLoaded.value = true; | |||
| } catch (error) { | |||
| console.error('加载发展历程失败:', error); | |||
| } finally { | |||
| isLoading.value = false; | |||
| } | |||
| }; | |||
| onMounted(async () => { | |||
| // 初始化时预加载数据 | |||
| await loadHistoryData(); | |||
| }); | |||
| </script> | |||
| <template> | |||
| <div class="about-page"> | |||
| <PageHeader | |||
| title="关于我们" | |||
| subtitle="了解瀚海黎明的故事与使命" | |||
| backgroundImage="https://images.unsplash.com/photo-1522071820081-009f0129c71c?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80" | |||
| /> | |||
| <section class="company-intro"> | |||
| <div class="container"> | |||
| <div class="intro-content" data-aos="fade-up"> | |||
| <h2>公司简介</h2> | |||
| <p>瀚海黎明成立于2020年,是一家专注于为企业提供高质量软件开发外包服务的技术公司。我们的团队由经验丰富的软件工程师、设计师和项目经理组成,致力于通过技术创新帮助企业实现数字化转型。</p> | |||
| <p>多年来,我们已成功为金融、医疗、教育、零售等多个行业的客户提供了定制化的软件解决方案,帮助他们提升运营效率,降低成本,增强市场竞争力。</p> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| <section class="company-history" data-aos="fade-up"> | |||
| <div class="container"> | |||
| <h2>发展历程</h2> | |||
| <div class="timeline" v-for="(item, index) in 5"> | |||
| <div class="timeline-item" data-aos="fade-right"> | |||
| <div class="year">2020</div> | |||
| <div class="event"> | |||
| <h3>公司成立</h3> | |||
| <p>瀚海黎明在深圳正式成立,开始提供软件开发服务</p> | |||
| <div class="about-page"> | |||
| <PageHeader :title="configParams.about_banner_title" :subtitle="configParams.about_banner_subtitle" | |||
| :backgroundImage="configParams.about_banner_bg" /> | |||
| <section class="company-tabs"> | |||
| <div class="container"> | |||
| <div class="tab-nav"> | |||
| <div class="tab" :class="{ active: activeTab === 'profile' }" @click="activeTab = 'profile'"> | |||
| <i class="fas fa-building"></i> 公司简介 | |||
| </div> | |||
| <div class="tab" | |||
| v-if="developmentHistory.length > 0" | |||
| :class="{ active: activeTab === 'history' }" @click="activeTab = 'history'"> | |||
| <i class="fas fa-history"></i> 发展历程 | |||
| </div> | |||
| <!-- <div class="tab" :class="{ active: activeTab === 'culture' }" @click="activeTab = 'culture'"> | |||
| <i class="fas fa-lightbulb"></i> 企业文化 | |||
| </div> --> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| <div v-if="activeTab === 'profile'"> | |||
| <section class="company-intro" v-if="configParams.about_company_profile"> | |||
| <div class="container"> | |||
| <div class="section-header" data-aos="fade-up"> | |||
| <h2><i class="fas fa-building"></i> 公司简介</h2> | |||
| <div class="separator"></div> | |||
| </div> | |||
| <div class="intro-wrapper"> | |||
| <div class="intro-content" data-aos="fade-up"> | |||
| <div class="content-card"> | |||
| <div class="card-decoration"> | |||
| <i class="fas fa-quote-left"></i> | |||
| </div> | |||
| <div v-html="configParams.about_company_profile"></div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| <section class="company-culture" data-aos="fade-up"> | |||
| <div class="container"> | |||
| <h2>企业文化</h2> | |||
| <div class="culture-values"> | |||
| <div class="value-item" data-aos="fade-up" data-aos-delay="100"> | |||
| <div class="value-icon"><!-- 图标 --></div> | |||
| <h3>创新</h3> | |||
| <p>不断探索新技术,为客户创造更大价值</p> | |||
| </div> | |||
| <!-- 其他价值观 --> | |||
| <div v-if="activeTab === 'history'"> | |||
| <section class="company-history" data-aos="fade-up"> | |||
| <div class="container"> | |||
| <div class="section-header" data-aos="fade-up"> | |||
| <h2><i class="fas fa-history"></i> 发展历程</h2> | |||
| <div class="separator"></div> | |||
| </div> | |||
| <!-- 加载状态 --> | |||
| <div v-if="isLoading" class="loading-container"> | |||
| <div class="loading-spinner"> | |||
| <i class="fas fa-circle-notch fa-spin"></i> | |||
| </div> | |||
| <p>正在加载发展历程...</p> | |||
| </div> | |||
| <!-- 无数据状态 --> | |||
| <div v-else-if="developmentHistory.length === 0" class="no-data-container"> | |||
| <i class="fas fa-info-circle"></i> | |||
| <p>暂无发展历程数据</p> | |||
| <button class="reload-btn" @click="loadHistoryData"> | |||
| <i class="fas fa-sync-alt"></i> 重新加载 | |||
| </button> | |||
| </div> | |||
| <!-- 数据显示 --> | |||
| <div v-else class="timeline-container"> | |||
| <div class="timeline" v-for="(item, index) in developmentHistory" :key="index"> | |||
| <div class="timeline-item" :class="{ 'right': index % 2 === 1 }" data-aos="fade-up"> | |||
| <div class="year"> | |||
| <span>{{ item.date }}</span> | |||
| <div class="dot"></div> | |||
| </div> | |||
| <div class="event"> | |||
| <h3>{{ item.title }}</h3> | |||
| <p>{{ item.content }}</p> | |||
| <i class="milestone-icon fas" :class="index % 4 === 0 ? 'fa-rocket' : | |||
| index % 4 === 1 ? 'fa-award' : | |||
| index % 4 === 2 ? 'fa-handshake' : 'fa-trophy'"></i> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <style lang="scss" scoped> | |||
| /* 导入全局SCSS变量 */ | |||
| @use '../../assets/scss/main.scss' as *; | |||
| @use "sass:color"; | |||
| .section-header { | |||
| text-align: center; | |||
| margin-bottom: 50px; | |||
| h2 { | |||
| font-size: 2.5rem; | |||
| color: $primary-color; | |||
| margin-bottom: 20px; | |||
| i { | |||
| margin-right: 12px; | |||
| } | |||
| } | |||
| .separator { | |||
| height: 3px; | |||
| width: 80px; | |||
| background: $primary-color; | |||
| margin: 0 auto; | |||
| } | |||
| } | |||
| .company-tabs { | |||
| background-color: white; | |||
| padding: 20px 0; | |||
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |||
| position: sticky; | |||
| top: 0; | |||
| z-index: 100; | |||
| .tab-nav { | |||
| display: flex; | |||
| justify-content: center; | |||
| gap: 20px; | |||
| .tab { | |||
| padding: 12px 24px; | |||
| border-radius: 30px; | |||
| cursor: pointer; | |||
| transition: all 0.3s ease; | |||
| font-weight: 600; | |||
| display: flex; | |||
| align-items: center; | |||
| /* 页面头部样式已移至PageHeader组件 */ | |||
| i { | |||
| margin-right: 8px; | |||
| } | |||
| &:hover { | |||
| background-color: rgba($primary-color, 0.1); | |||
| } | |||
| &.active { | |||
| background-color: $primary-color; | |||
| color: white; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .company-intro { | |||
| padding: 80px 0; | |||
| background-color: white; | |||
| padding: 100px 0; | |||
| background-color: white; | |||
| position: relative; | |||
| overflow: hidden; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| width: 300px; | |||
| height: 300px; | |||
| background: rgba($primary-color, 0.05); | |||
| border-radius: 50%; | |||
| top: -150px; | |||
| left: -150px; | |||
| z-index: 0; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| width: 200px; | |||
| height: 200px; | |||
| background: rgba($primary-color, 0.05); | |||
| border-radius: 50%; | |||
| bottom: -100px; | |||
| right: -100px; | |||
| z-index: 0; | |||
| } | |||
| .intro-wrapper { | |||
| position: relative; | |||
| z-index: 1; | |||
| max-width: 900px; | |||
| margin: 0 auto; | |||
| @media (max-width: 992px) { | |||
| padding: 0 20px; | |||
| } | |||
| } | |||
| .intro-content { | |||
| line-height: 1.8; | |||
| .content-card { | |||
| background: white; | |||
| border-radius: 15px; | |||
| padding: 40px 50px; | |||
| box-shadow: 0 15px 40px rgba(0, 0, 0, 0.08); | |||
| position: relative; | |||
| border-top: 5px solid $primary-color; | |||
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |||
| &:hover { | |||
| transform: translateY(-5px); | |||
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.12); | |||
| } | |||
| .card-decoration { | |||
| position: absolute; | |||
| top: -25px; | |||
| left: 50px; | |||
| width: 50px; | |||
| height: 50px; | |||
| background: $primary-color; | |||
| border-radius: 50%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| color: white; | |||
| font-size: 24px; | |||
| box-shadow: 0 5px 15px rgba($primary-color, 0.5); | |||
| } | |||
| div[v-html] { | |||
| font-size: 1.1rem; | |||
| color: $text-color; | |||
| line-height: 1.8; | |||
| margin-top: 10px; | |||
| ::v-deep(h3) { | |||
| color: $primary-color; | |||
| margin-top: 0; | |||
| font-size: 1.8rem; | |||
| position: relative; | |||
| display: inline-block; | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| bottom: -8px; | |||
| left: 0; | |||
| width: 50px; | |||
| height: 3px; | |||
| background: $primary-color; | |||
| } | |||
| } | |||
| ::v-deep(p) { | |||
| margin-bottom: 1.2rem; | |||
| } | |||
| ::v-deep(strong) { | |||
| color: color.adjust($text-color, $lightness: -25%); | |||
| font-weight: 600; | |||
| } | |||
| ::v-deep(ul) { | |||
| padding-left: 20px; | |||
| li { | |||
| margin-bottom: 10px; | |||
| position: relative; | |||
| &::before { | |||
| content: '•'; | |||
| color: $primary-color; | |||
| font-weight: bold; | |||
| display: inline-block; | |||
| width: 1em; | |||
| margin-left: -1em; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 20px; | |||
| right: 20px; | |||
| width: 60px; | |||
| height: 60px; | |||
| background: rgba($primary-color, 0.05); | |||
| border-radius: 50%; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| bottom: 30px; | |||
| left: 30px; | |||
| width: 40px; | |||
| height: 40px; | |||
| border: 3px solid rgba($primary-color, 0.1); | |||
| border-radius: 50%; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .company-history { | |||
| padding: 80px 0; | |||
| background-color: $light-bg; | |||
| padding: 80px 0; | |||
| background-color: $light-bg; | |||
| .timeline-container { | |||
| position: relative; | |||
| max-width: 1000px; | |||
| margin: 60px auto 0; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 50%; | |||
| width: 2px; | |||
| background: $primary-color; | |||
| transform: translateX(-50%); | |||
| } | |||
| } | |||
| .timeline-item { | |||
| position: relative; | |||
| margin-bottom: 60px; | |||
| width: 50%; | |||
| padding-right: 40px; | |||
| &.right { | |||
| margin-left: 50%; | |||
| padding-right: 0; | |||
| padding-left: 40px; | |||
| .year { | |||
| right: auto; | |||
| left: -100px; | |||
| text-align: right; | |||
| } | |||
| .dot { | |||
| right: auto; | |||
| left: -9px; | |||
| } | |||
| } | |||
| .year { | |||
| position: absolute; | |||
| right: -100px; | |||
| top: 15px; | |||
| font-size: 24px; | |||
| font-weight: bold; | |||
| color: $primary-color; | |||
| width: 80px; | |||
| text-align: left; | |||
| } | |||
| .dot { | |||
| position: absolute; | |||
| right: -9px; | |||
| top: 20px; | |||
| width: 16px; | |||
| height: 16px; | |||
| border-radius: 50%; | |||
| background: $primary-color; | |||
| border: 3px solid white; | |||
| box-shadow: 0 0 0 4px rgba($primary-color, 0.3); | |||
| } | |||
| .event { | |||
| background: white; | |||
| padding: 25px; | |||
| border-radius: 10px; | |||
| box-shadow: $shadow-sm; | |||
| position: relative; | |||
| h3 { | |||
| margin-top: 0; | |||
| color: $primary-color; | |||
| } | |||
| p { | |||
| margin-bottom: 0; | |||
| line-height: 1.6; | |||
| } | |||
| .milestone-icon { | |||
| position: absolute; | |||
| top: 20px; | |||
| right: 20px; | |||
| font-size: 24px; | |||
| color: rgba($primary-color, 0.2); | |||
| } | |||
| } | |||
| @media (max-width: 768px) { | |||
| width: 100%; | |||
| padding-right: 0; | |||
| padding-left: 40px; | |||
| margin-left: 0; | |||
| &.right { | |||
| margin-left: 0; | |||
| } | |||
| .year { | |||
| left: -20px !important; | |||
| right: auto !important; | |||
| top: -40px; | |||
| text-align: left !important; | |||
| } | |||
| .dot { | |||
| left: -9px !important; | |||
| right: auto !important; | |||
| } | |||
| } | |||
| } | |||
| @media (max-width: 768px) { | |||
| .timeline-container::before { | |||
| left: 0; | |||
| } | |||
| } | |||
| } | |||
| .timeline { | |||
| position: relative; | |||
| max-width: 800px; | |||
| margin: 40px auto 0; | |||
| padding: 20px 0; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 0; | |||
| bottom: 0; | |||
| left: 50%; | |||
| width: 2px; | |||
| background: $primary-color; | |||
| transform: translateX(-50%); | |||
| } | |||
| .timeline-item { | |||
| position: relative; | |||
| margin-bottom: 50px; | |||
| display: flex; | |||
| align-items: center; | |||
| .company-culture { | |||
| padding: 80px 0; | |||
| background-color: white; | |||
| .culture-values { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |||
| gap: 30px; | |||
| margin-top: 40px; | |||
| .value-item { | |||
| text-align: center; | |||
| padding: 40px 30px; | |||
| background: $light-bg; | |||
| border-radius: 15px; | |||
| transition: all 0.3s ease; | |||
| box-shadow: $shadow-sm; | |||
| border-bottom: 4px solid transparent; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| box-shadow: $shadow-md; | |||
| border-bottom: 4px solid $primary-color; | |||
| } | |||
| .value-icon { | |||
| margin-bottom: 20px; | |||
| i { | |||
| font-size: 36px; | |||
| color: $primary-color; | |||
| background: rgba($primary-color, 0.1); | |||
| width: 80px; | |||
| height: 80px; | |||
| line-height: 80px; | |||
| border-radius: 50%; | |||
| } | |||
| } | |||
| h3 { | |||
| margin-bottom: 15px; | |||
| color: $primary-color; | |||
| } | |||
| p { | |||
| color: $text-color; | |||
| line-height: 1.6; | |||
| } | |||
| } | |||
| } | |||
| .culture-mission { | |||
| margin-top: 60px; | |||
| display: grid; | |||
| grid-template-columns: 1fr 1fr; | |||
| gap: 30px; | |||
| @media (max-width: 768px) { | |||
| grid-template-columns: 1fr; | |||
| } | |||
| .mission-card { | |||
| background: linear-gradient(135deg, $primary-color, color.adjust($primary-color, $lightness: -15%)); | |||
| border-radius: 15px; | |||
| overflow: hidden; | |||
| box-shadow: $shadow-md; | |||
| color: white; | |||
| .card-header { | |||
| padding: 25px; | |||
| display: flex; | |||
| align-items: center; | |||
| i { | |||
| font-size: 32px; | |||
| margin-right: 15px; | |||
| } | |||
| h3 { | |||
| margin: 0; | |||
| font-size: 1.8rem; | |||
| } | |||
| } | |||
| .card-content { | |||
| background: rgba(255, 255, 255, 0.1); | |||
| padding: 25px; | |||
| p { | |||
| margin: 0; | |||
| line-height: 1.8; | |||
| font-size: 1.1rem; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .loading-container, | |||
| .no-data-container { | |||
| text-align: center; | |||
| padding: 60px 0; | |||
| .year { | |||
| flex: 0 0 100px; | |||
| text-align: right; | |||
| font-size: 24px; | |||
| font-weight: bold; | |||
| color: $primary-color; | |||
| padding-right: 30px; | |||
| i { | |||
| font-size: 40px; | |||
| color: $primary-color; | |||
| margin-bottom: 20px; | |||
| } | |||
| .event { | |||
| flex: 1; | |||
| background: white; | |||
| padding: 20px; | |||
| border-radius: 8px; | |||
| box-shadow: $shadow-sm; | |||
| margin-left: 30px; | |||
| } | |||
| } | |||
| p { | |||
| font-size: 1.2rem; | |||
| color: $text-color; | |||
| margin-bottom: 20px; | |||
| } | |||
| } | |||
| .company-culture { | |||
| padding: 80px 0; | |||
| background-color: white; | |||
| .culture-values { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |||
| gap: 30px; | |||
| margin-top: 40px; | |||
| .loading-spinner { | |||
| margin-bottom: 20px; | |||
| .value-item { | |||
| text-align: center; | |||
| padding: 30px; | |||
| background: $light-bg; | |||
| border-radius: 8px; | |||
| transition: transform 0.3s ease; | |||
| &:hover { | |||
| transform: translateY(-10px); | |||
| } | |||
| } | |||
| } | |||
| i { | |||
| font-size: 40px; | |||
| color: $primary-color; | |||
| } | |||
| } | |||
| .reload-btn { | |||
| background-color: $primary-color; | |||
| color: white; | |||
| border: none; | |||
| padding: 10px 20px; | |||
| border-radius: 30px; | |||
| font-size: 1rem; | |||
| cursor: pointer; | |||
| transition: all 0.3s ease; | |||
| box-shadow: 0 5px 15px rgba($primary-color, 0.3); | |||
| &:hover { | |||
| background-color: color.adjust($primary-color, $lightness: -10%); | |||
| transform: translateY(-2px); | |||
| } | |||
| i { | |||
| font-size: 1rem; | |||
| color: white; | |||
| margin-right: 8px; | |||
| margin-bottom: 0; | |||
| } | |||
| } | |||
| </style> | |||
| @ -1,7 +1,29 @@ | |||
| import { defineConfig } from 'vite' | |||
| import vue from '@vitejs/plugin-vue' | |||
| import { defineConfig, loadEnv } from 'vite'; | |||
| import path from 'path'; | |||
| // https://vite.dev/config/ | |||
| export default defineConfig({ | |||
| plugins: [vue()], | |||
| export default defineConfig(({ command, mode }) => { | |||
| const env = loadEnv(mode, process.cwd()); | |||
| return { | |||
| plugins: [vue()], | |||
| resolve: { | |||
| alias: { | |||
| '@': path.resolve(__dirname, 'src') | |||
| } | |||
| }, | |||
| server: { | |||
| host: '0.0.0.0', | |||
| port: env.VITE_APP_PORT ? Number(env.VITE_APP_PORT) : 3000, | |||
| open: true, | |||
| proxy: { | |||
| [env.VITE_APP_BASE_API]: { | |||
| target: 'http://localhost:8080', | |||
| changeOrigin: true, | |||
| ws: true, | |||
| rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') | |||
| } | |||
| } | |||
| }, | |||
| } | |||
| }) | |||