档案管理系统源码实战:著录、归档、检索与数据权限

发布时间:2026/10/31 13:24:47
档案管理系统源码实战:著录、归档、检索与数据权限 简介「世纪科怡档案管理」是一套面向企事业单位档案人员、行政文秘及信息化管理者的专业档案管理软件安装包适合需要集中收集、整理、检索与保管文档资料的场景也可供研究老旧档案系统部署与兼容性的技术人员参考。压缩包共48个文件约48.34MB涵盖可执行程序、动态链接库、安装配置与说明文档、图像资源及数据文件等类型以bmp、vts、dll为主另有exe、cab、dat、txt、pdf等构成较完整的安装与说明体系。已有547人浏览学习说明其在档案管理工具类资源中具备一定关注度。读者可从中获得原版安装包及配套说明书了解该软件在权限分级、版本留存、备份恢复等方面的功能设计并借助说明文档完成部署与功能验证对需要复现旧版档案系统环境或评估档案管理流程的读者具有实际参考价值。1. 档案管理系统的落地场景从一堆扫描件到可检索的电子档案去年帮一个档案室做数字化最开始的诉求并不是“上个系统”而是“这几万页扫描件怎么让人找得到”。他们当时的状态很典型扫描件按年度文件夹堆在共享盘里命名靠人工找一份 2016 年的会议纪要得先问人、再翻目录、最后挨个打开 PDF 看。档案管理这件事难点从来不在“存”而在“著录规范 检索命中 借阅可控”这三件事同时成立。这套档案管理系统源码包解决的就是这个闭环全宗、案卷、文件三级著录归档状态流转组合条件检索再加上按全宗划分的数据权限和借阅留痕。适合两类人一类是单位内部要做档案数字化的开发者拿来改改字段就能用另一类是想看国产档案业务系统怎么落地的后端著录表单和权限模型这两块值得拆开看。下面按“骨架—核心链路—跑起来—避坑—二次开发”的顺序讲。2. 技术栈与目录结构先看清这套档案管理源码的骨架拿到一个业务系统源码我习惯先干三件事看依赖、看目录、看表结构。这三样看完基本能判断它是不是能改、改起来会不会崩。档案管理系统的业务特征很鲜明——著录字段多、附件大、检索条件组合爆炸、写少读多、审计要求高。这些特征直接决定了技术选型不该往“炫技”方向走。2.1 技术选型与分层为什么档案类系统偏爱「单体 关系库」这类系统常见的技术组合是 Spring Boot MyBatis-Plus MySQL Redis Vue/Element UI附件存本地磁盘或对象存储全文检索视数据量决定要不要上 Elasticsearch。我一般不建议一上来就拆微服务档案系统的模块耦合点集中在数据权限和著录元数据上全宗、案卷、文件三级模型之间全是关联查询拆开之后跨服务 JOIN 和分布式事务的成本远大于收益。单体 清晰的包分层反而是这个场景下最省心的做法。分层上通常是 controller / service / mapper / entity / dto / vo 六层外加 common 放统一返回、异常、拦截器。配置文件里最关键的是三块数据源、文件上传大小、Jackson 时区。这三块配错后面调试能折腾半天。# application.yml 里最该先确认的几项 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/archive_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 100MB # 单个扫描件上限按实际扫描分辨率调整 max-request-size: 200MB # 一次批量上传的总上限 jackson: time-zone: GMT8 # 不写这行归档时间可能整体偏 8 小时 date-format: yyyy-MM-dd HH:mm:ss archive: storage: type: local # local 或 oss切换实现类即可 local-path: /data/archive/filesserverTimezone和jackson.time-zone这两项是档案系统的高频翻车点因为归档时间、借阅时间都要进审计差 8 小时在报表上非常显眼。max-file-size别照抄要看扫描件的实际大小——A4 彩色 300dpi 的 PDF 单页能到 2MB 以上一卷几十页就是上百兆。数据库层面政务和事业单位环境里常见 MySQL、达梦、人大金仓混用。我的做法是把 SQL 收敛在 Mapper 层尽量不用数据库方言重的函数日期截取、字符串拼接这类操作放到 Java 里做。这样换库时改动面可控不至于满项目找DATE_FORMAT。2.2 目录结构逐层拆从启动类到全宗模块源码包的目录结构大致是这样后端和前端分开前端独立构建后丢给 Nginxarchive-parent/ ├── archive-common/ # 统一返回、异常、常量、工具类 ├── archive-admin/ # 启动模块controller 与配置都在这里 │ ├── src/main/java/.../controller │ │ ├── FondsController.java # 全宗管理 │ │ ├── ArchiveFileController.java # 案卷著录与归档 │ │ ├── ArchiveItemController.java # 文件级条目 │ │ ├── BorrowController.java # 借阅申请与审批 │ │ └── SearchController.java # 综合检索 │ ├── src/main/java/.../service/impl │ └── src/main/resources │ ├── application.yml │ └── mapper/ # MyBatis XML复杂检索 SQL 放这里 ├── archive-system/ # 用户、角色、菜单、数据权限 └── archive-ui/ # Vue 前端著录表单在 views/archive 下判断一个档案系统好不好改看两个地方mapper/目录里 XML 的数量和前端著录表单是不是配置化生成的。XML 多说明复杂查询没硬塞进注解改检索条件时不用动 Java著录表单如果是写死的几十个el-form-item那加字段就是体力活得提前有心理准备。启动类在archive-admin下跑之前确认MapperScan扫到了archive-system的 mapper 包跨模块扫描漏配是新手最常见的启动失败原因。2.3 数据库表设计全宗、案卷、文件三级模型怎么落表档案业务的核心模型就三层全宗一个立档单位的全部档案→ 案卷一组相关文件→ 文件最小著录单位。表设计上通常再加附件表、借阅表、操作日志表。下面这几张是骨架字段做了精简实际项目里著录项会多得多。-- 全宗表档案的最高归属单位数据权限就挂在这一层 CREATE TABLE arc_fonds ( id BIGINT PRIMARY KEY AUTO_INCREMENT, fonds_code VARCHAR(32) NOT NULL COMMENT 全宗号单位内唯一, fonds_name VARCHAR(128) NOT NULL COMMENT 全宗名称, org_id BIGINT NOT NULL COMMENT 归属机构关联 sys_dept, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_fonds_code (fonds_code) ) COMMENT 全宗; -- 案卷表一卷档案归档状态和保管期限在这一层 CREATE TABLE arc_file ( id BIGINT PRIMARY KEY AUTO_INCREMENT, fonds_code VARCHAR(32) NOT NULL COMMENT 所属全宗号, file_no VARCHAR(64) NOT NULL COMMENT 案卷号, title VARCHAR(255) NOT NULL COMMENT 案卷题名, year SMALLINT NOT NULL COMMENT 年度, security_level TINYINT DEFAULT 1 COMMENT 1公开 2内部 3秘密, retain_period VARCHAR(8) DEFAULT D30 COMMENT 保管期限 D30/D10/Y, status TINYINT DEFAULT 0 COMMENT 0草稿 1待归档 2已归档 3借出, archive_time DATETIME NULL COMMENT 正式归档时间, create_by BIGINT NOT NULL, UNIQUE KEY uk_fonds_file_no (fonds_code, file_no) ) COMMENT 案卷; -- 文件表真正被检索和借阅的对象附件挂在这里 CREATE TABLE arc_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, file_id BIGINT NOT NULL COMMENT 所属案卷 id, item_no VARCHAR(64) NOT NULL COMMENT 件号, title VARCHAR(255) NOT NULL COMMENT 文件题名, doc_date DATE NULL COMMENT 成文日期, page_count INT DEFAULT 0, attach_path VARCHAR(512) NULL COMMENT 主附件相对路径, ocr_text MEDIUMTEXT NULL COMMENT OCR 文本供关键词检索, KEY idx_file_id (file_id), KEY idx_title (title) ) COMMENT 文件条目;status这个字段是全篇的枢纽归档、借阅、检索都会看它。很多二次开发改着改着就在业务代码里散落一堆if (status 2)后面加一个“已移交”状态就到处漏改所以第 3 章会把它收成一个显式状态机。ocr_text是加分项扫描件没有文字层的时候检索基本靠题名体验很差接了 OCR 之后把文本存进来配合LIKE或全文索引命中率会明显不一样。3. 核心模块拆解著录、归档、检索三条主链路这一章是整套系统最值得看的部分。著录决定数据质量归档决定数据能不能冻结检索决定用户愿不愿意用。三条链路串起来就是一份档案从录入到被找到的完整生命周期。3.1 著录表单元数据字段怎么校验与落库著录是数据入口校验必须放在后端前端校验只是体验优化。常见做法是 DTO 上挂javax.validation注解Controller 加ValidService 里再做业务级校验比如案卷号在同全宗内不能重复。下面这段是文件级著录的入口。RestController RequestMapping(/archive/item) public class ArchiveItemController { Resource private ArchiveItemService itemService; /** * 新增或更新一条文件级著录 * param dto 前端表单itemId 为空走新增不为空走更新 * return 主键 id */ PostMapping(/save) public ResultLong save(RequestBody Valid ArchiveItemDTO dto) { // 归档后的数据不允许再改这里直接拦掉 itemService.checkEditable(dto.getFileId()); return Result.ok(itemService.saveOrUpdateItem(dto)); } } public class ArchiveItemDTO { private Long id; NotNull(message 所属案卷不能为空) private Long fileId; NotBlank(message 文件题名不能为空) Size(max 255, message 题名长度不能超过 255) private String title; Pattern(regexp ^\\d{4}-\\d{2}-\\d{2}$, message 成文日期格式应为 yyyy-MM-dd) private String docDate; Min(value 0, message 页数不能为负) private Integer pageCount; }checkEditable的判定逻辑放在 Service 里先查案卷状态status 2直接抛业务异常状态已归档不允许修改。这个顺序很重要——先校验可编辑性再做字段落库否则前端并发提交两个请求后到的会把已归档的案卷字段覆盖掉。落库时的几个细节容易忽略docDate前端传字符串DTO 里用 String 接收再LocalDate.parse比直接用LocalDate接收少踩格式化的坑attachPath存相对路径而不是绝对路径换服务器部署时不用改数据ocr_text建议单独异步写入别卡在保存请求里扫描件 OCR 动辄几秒同步做会让用户以为页面卡死。案卷题名的自动生成也值得一提。档案著录规则里题名往往由“责任者 事由 文种”拼接实际项目里常见做法是前端提供“自动生成”按钮后端拼好后回填允许人工再改。纯自动不加人工确认遇到特殊文种就会生成一堆读不通的题名。3.2 归档流程从待归档到入库的状态机归档是档案系统区别于普通文档管理的关键动作一旦归档数据冻结、生成档号、写入归档时间、进入只读。很多源码包在这里写得很随意状态字段到处 set二次开发几乎无法维护。合格的写法是枚举 单一流转方法。public enum ArchiveStatus { DRAFT(0, 草稿), PENDING(1, 待归档), ARCHIVED(2, 已归档), BORROWED(3, 借出); private final int code; private final String label; // 构造与 getter 省略 } Service public class ArchiveFlowService { /** * 执行归档草稿/待归档 - 已归档 * param fileId 案卷 id * param archiveNo 档号由前端或规则生成 */ Transactional(rollbackFor Exception.class) public void archive(Long fileId, String archiveNo) { ArchiveFile file fileMapper.selectById(fileId); if (file null) { throw new BizException(案卷不存在); } int current file.getStatus(); // 只允许从草稿或待归档进入已归档 if (current ! ArchiveStatus.DRAFT.getCode() current ! ArchiveStatus.PENDING.getCode()) { throw new BizException(当前状态不允许归档 current); } // 档号唯一性再校验一次防止并发下重复 Long dup fileMapper.countByArchiveNo(archiveNo); if (dup ! null dup 0) { throw new BizException(档号已存在 archiveNo); } file.setStatus(ArchiveStatus.ARCHIVED.getCode()); file.setArchiveNo(archiveNo); file.setArchiveTime(LocalDateTime.now()); fileMapper.updateById(file); // 归档动作写审计日志借阅和检索都要用到 auditLogService.record(ARCHIVE, fileId, archiveNo); } }这段代码的价值在于把“允许从哪些状态进来”写死了。之后要加“已移交”状态只需要在枚举里加一项、在archive方法里补一条判断不用满项目搜setStatus(2)。Transactional也不能省状态更新和审计日志必须同生共死日志丢了事后追查归档记录会很麻烦。档号生成规则各单位的差异很大常见的是“全宗号-年度-保管期限-件号”源码包里一般给了个默认实现放在ArchiveNoGenerator里改规则只动这一个类就行。规则里最容易出错的是件号自增的并发安全单机部署时用数据库唯一索引兜底即可多节点部署就得考虑号段或 Redis 自增。3.3 检索模块组合条件 全文检索的实现检索是用户感知最强的模块。档案系统的检索条件通常是全宗、年度、题名关键词、责任者、文种、保管期限、密级、成文日期区间再叠加分页和排序。组合条件多别用字符串拼 SQL用 MyBatis-Plus 的条件构造器可读性和安全性都好。public PageArchiveItemVO search(SearchDTO dto, PageArchiveItemVO page) { LambdaQueryWrapperArchiveItem wrapper Wrappers.lambdaQuery(); wrapper.eq(StringUtils.hasText(dto.getFondsCode()), ArchiveItem::getFondsCode, dto.getFondsCode()); wrapper.eq(dto.getYear() ! null, ArchiveItem::getYear, dto.getYear()); wrapper.eq(StringUtils.hasText(dto.getSecurityLevel()), ArchiveItem::getSecurityLevel, dto.getSecurityLevel()); // 题名和 OCR 文本一起匹配扫描件也能命中 if (StringUtils.hasText(dto.getKeyword())) { String kw dto.getKeyword().trim(); wrapper.and(w - w.like(ArchiveItem::getTitle, kw) .or() .like(ArchiveItem::getOcrText, kw)); } // 成文日期区间前端传 yyyy-MM-dd if (StringUtils.hasText(dto.getDateFrom())) { wrapper.ge(ArchiveItem::getDocDate, LocalDate.parse(dto.getDateFrom())); } if (StringUtils.hasText(dto.getDateTo())) { wrapper.le(ArchiveItem::getDocDate, LocalDate.parse(dto.getDateTo())); } wrapper.orderByDesc(ArchiveItem::getDocDate); return itemMapper.selectPage(page, wrapper); }wrapper.and(w - ...)这段是重点题名和 OCR 的条件必须包在一组括号里否则会和外层的全宗、年度条件形成错误的or优先级搜出来的结果跨全宗这在数据权限场景里是事故。like前面不加通配符索引就用不上数据量上万条之后会明显变慢数据再大就得挂 Elasticsearch把ocr_text同步过去做分词检索。分页对象Page要配合分页插件使用MybatisPlusInterceptor没注册的话selectPage会退化成全量查询再内存分页数据量一大直接超时。这个坑在第 5 章还会展开。检索结果返回 VO 而不是实体主要是为了脱敏实体里有attach_path这类内部字段不该直接吐给前端。VO 里只保留题名、档号、年度、密级、附件是否可预览这些。3.4 借阅与权限谁能看哪个全宗的哪一卷档案的权限是两层功能权限能不能进借阅模块和数据权限能看哪些全宗。功能权限靠角色菜单数据权限靠全宗授权。很多源码包只做了前者结果就是任何登录用户都能搜到全部档案这在真实场景里是不能交付的。常见做法是在用户和全宗之间建一张授权表登录后把可访问的全宗号列表放进上下文检索和详情接口都拼上这个条件。// 登录后缓存的上下文key 为 userId public class ArchiveContext { public static ListString allowedFonds() { LoginUser user SecurityUtils.getLoginUser(); // 管理员放行全部 if (user.isAdmin()) { return Collections.emptyList(); } return user.getFondsCodes(); } } // 在检索条件里追加数据权限 ListString allowed ArchiveContext.allowedFonds(); if (!allowed.isEmpty()) { wrapper.in(ArchiveItem::getFondsCode, allowed); }allowedFonds()返回空列表时表示不限制这个约定要在团队里说清楚否则新人很容易把“空”理解成“无权限”然后加个if (allowed.isEmpty()) return empty管理员反而什么都搜不到。借阅模块在此基础上再加一层状态校验密级高于用户级别的档案申请借阅必须走审批审批通过后才把案卷状态置为“借出”归还时再改回来。借阅表里建议存申请时间、审批时间、归还时间、审批人四个字段别只存一个状态。事后审计要查“谁在什么时候批的”只靠日志表拼不出来。4. 本地跑起来环境准备、初始化与首次登录源码跑不起来后面什么都是空谈。这一章按我实际的启动顺序写环境、建库、配置、启动四步每步都标了容易卡住的地方。4.1 环境与依赖版本对齐先看pom.xml里的 Spring Boot 版本和 JDK 版本要求这套系统常见的是 JDK 8 或 11 Spring Boot 2.x前端 Vue 2 Node 14/16。版本对不上前端npm install会因为 node-sass 直接报错这是最高频的启动失败原因。# 后端环境确认 java -version # 1.8 或 11按 pom 里的 java.version 来 mvn -v # 3.6 mysql --version # 5.7 或 8.0 # 前端环境确认node 版本别乱升 node -v # 14.x 或 16.x npm -v # 前端依赖安装node-sass 装不上时先换镜像再重试 cd archive-ui npm install --registryhttps://registry.npmmirror.comRedis 是否必须看application.yml里有没有配。会话、验证码、字典缓存用到 Redis 的话本地不启动会直接连不上。国产数据库适配版本另有 profile本地调试用 MySQL 就行别一上来就纠结换库。4.2 建库与初始化数据建库、导 SQL、改连接串三步。SQL 文件一般在doc/sql或resources/sql下分结构脚本和数据脚本两个。# 建库字符集必须是 utf8mb4否则生僻字和特殊符号会丢 mysql -uroot -p -e CREATE DATABASE archive_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入表结构和初始数据注意顺序 mysql -uroot -p archive_db doc/sql/archive_schema.sql mysql -uroot -p archive_db doc/sql/archive_data.sql # 确认初始账号和菜单权限已写入 mysql -uroot -p archive_db -e SELECT user_name, status FROM sys_user;初始数据里通常有一个管理员账号和一套菜单。导入后如果登录报“用户不存在”先查sys_user表是不是空的如果登录成功但左侧菜单空白那是角色菜单关联表没导全补sys_role_menu的数据即可。这两类问题占了初始化阶段的大半。utf8mb4别省。档案题名里有生僻字、书名号、特殊符号很常见用utf8会在入库时截断甚至报错。4.3 前后端启动与常见配置改动后端启动前把application.yml里的数据库地址、账号、文件存储路径改成本机的。文件存储路径要提前建好目录程序不会自动创建。# 后端启动指定 dev profile mvn -pl archive-admin -am spring-boot:run -Dspring-boot.run.profilesdev # 或者打包后运行 mvn clean package -DskipTests java -jar archive-admin/target/archive-admin.jar --spring.profiles.activedev前端开发模式下改vue.config.js里的代理目标指到后端端口然后npm run serve。生产部署时npm run build出静态文件丢给 Nginx同时把/api反向代理到后端顺手把client_max_body_size调大不然上传大附件会返回 413。首次登录后第一件事是改管理员密码第二件事是建一个测试全宗和一卷测试档案把著录—归档—检索整条链路手动走一遍。这一步花十分钟能提前暴露掉大部分配置问题。注意application.yml里的文件存储路径如果配成相对路径启动目录不同会导致附件找不到。统一用绝对路径或者配成${user.home}/archive/files这种确定值。5. 避坑与排查档案系统上线前最容易翻车的几处下面这几条都是我在类似项目里真实遇到过的按“现象—原因—解决”记遇到时照着对。5.1 附件下载文件名变成乱码现象下载扫描件时浏览器保存下来的文件名是一串%E4%BC%9A%E8%AE%AE...或者干脆是download。原因Content-Disposition响应头里直接塞了中文没做编码不同浏览器解析行为不一致。解决文件名统一 URL 编码并把替换成%20因为URLEncoder会把空格编成部分浏览器不认。String rawName URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) .replace(, %20); response.setHeader(Content-Disposition, attachment; filename\ rawName \; filename*UTF-8 rawName);5.2 大附件上传超时或 413现象几十兆的扫描件上传到一半失败或者直接返回 413小文件正常。原因三层限制Spring 的 multipart 配置、Nginx 的client_max_body_size、网关超时。只改一层不够。解决三处一起改。Spring 侧调max-file-size和max-request-sizeNginx 侧加client_max_body_size 200m;并调proxy_read_timeout如果走的是分片上传还要确认合并分片的临时目录有写权限。5.3 归档时间整体差 8 小时现象页面上显示的归档时间比实际早了或晚了 8 小时导出报表时更明显。原因数据库连接串没带serverTimezone或者 Jackson 没配时区两者只配一个也会偏。解决连接串加serverTimezoneAsia/Shanghaispring.jackson.time-zone配GMT8同时确认 MySQL 服务器本身的时区。三处对齐之后再测别只改一处就以为好了。5.4 换个账号就能看到别的全宗档案现象前端菜单已经隐藏了但直接在地址栏输详情页 URL或者在检索接口里改个fondsCode参数照样能拿到数据。原因权限只做在菜单和按钮上接口层没校验全宗归属。前端隐藏从来不是安全边界。解决在 Service 层统一拦截详情、更新、删除、下载附件四个入口都要校验当前用户对该全宗是否有权限。别在 Controller 里零散写抽一个checkFondsPermission(fondsCode)方法所有入口调它。5.5 分页查出来的总数不对现象列表只显示 10 条但总条数显示成全部数据量翻页也乱。原因MyBatis-Plus 的分页插件没注册selectPage退化成内存分页或者旧版本用了PaginationInterceptor而新版本要求MybatisPlusInterceptor。解决确认配置类里注册的是MybatisPlusInterceptor并添加了PaginationInnerInterceptor(DbType.MYSQL)。换过 MyBatis-Plus 大版本的项目这里最容易残留旧写法导致不生效。6. 二次开发把档案系统改成自己单位的业务口径真正落地时几乎没人会原样用。最常见的三类改动是加著录字段、改档号规则、加自定义报表。加字段的正确顺序是先加表列带默认值避免历史数据为 null 导致查询报错再改实体和 DTO最后改前端表单。前端如果是配置化生成的改 JSON 配置就行如果是写死的el-form-item记得把校验规则一起补上不然前端过了后端挂。档号规则改动集中在ArchiveNoGenerator改完必须回归三件事新归档的档号格式对不对、历史档号有没有被影响、并发归档时会不会生成重复号。第三点最容易被忽略单机测试永远复现不出来。自定义报表我一般不给每个报表写一个 Controller而是做一个通用导出查询条件复用检索 DTO导出时把Page换成不分页的List用 EasyExcel 流式写避免几万条数据把内存打满。导出字段做白名单防止把attach_path这类内部字段带出去。验证方法上我习惯准备一份回归清单每次改完照着走一遍验证项操作预期著录校验题名为空提交后端返回明确错误不落库归档冻结归档后再次编辑提示已归档不允许修改数据权限换普通账号搜其他全宗结果为空详情接口 403附件链路上传 100MB PDF 后下载成功文件名正常分页检索结果翻到第二页条数与总数一致有一回我改完档号规则直接提测忘了跑并发归档那条结果上线第二天就出现两卷同号靠人工改数据补了半下午。从那以后我每次动档号、状态机、数据权限这三块都强制把回归清单从头走一遍哪怕改动只有一行。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询