法律文书生成系统 - 项目博客文档集合

179 阅读30分钟
法律文书生成系统,旨在与AI结合将普通民众的法律需求智能化、现代化,满足广大人民对于基本法律援助的需要,自动完成案情分析、法条推荐和法律文书生成,保障人民权益。本小组

一、系统目标定位与分析

1.1 需求收集

需求收集能帮助全面、准确地了解用户需求,能够确保开发出的产品符合用户期望并具备市场竞争力。

1. 故事板设置

通俗易懂展示项目对于个人用户的作用

image.png

2. 用户故事收集

通过与用户访谈,了解他们的需求和痛点。问他们在工作中的具体任务、面临的挑战以及希望系统提供哪些支持。并观察用户在实际环境中使用现有系统或完成相关任务,发现潜在需求。

  1. 用户角色1:律师

    用户故事1:

    作为 一名律师  
    我希望 通过法律文书生成系统快速生成标准化的合同模板  
    以便 节省时间并确保合同内容的准确性和法律合规性
    

    验收标准:

    律师可以选择不同类型的合同模板(如租赁合同、劳务合同等)。  
    系统提供标准化的合同条款,律师可以根据具体情况进行修改。  
    系统自动检查合同的法律合规性,并提示可能存在的问题。  
    合同生成后可以下载和打印。  
    
  2. 用户角色2:个人用户

    用户故事2:

    作为 一名需要法律文书的个人用户  
    我希望 能通过简单的问题回答生成法律文书  
    以便 我能在不懂法律的情况下完成文书的生成
    

    验收标准:

    系统提供一个问答式的界面,引导用户输入必要的信息(如姓名、地址、合同内容等)。  
    根据用户的回答,系统自动生成相应的法律文书。  
    提供文书预览和修改功能,确保用户满意。  
    文书生成后可以下载、打印或通过电子邮件发送
    
  3. 用户角色2:个人用户

    用户故事3:

    作为 一名需要法律文书的个人用户  
    我希望 系统能提供法律咨询和帮助  
    以便 我能了解如何正确使用生成的文书
    

    验收标准:

    系统集成法律咨询服务,可以通过聊天或电话与律师沟通。  
    提供常见问题和解答的知识库,用户可以自行查阅。  
    系统能推荐相关的法律文书和使用指南。  
    用户可以预约律师进行更详细的法律咨询。
    

3. 问卷调差

  1. 主题设置:用户对新软件产品的需求和期望

  2. 涵盖范围设置:

    1. 使用场景论述
    2. 用户故事收集
    3. 性能需求论述
    4. 现场原型试用反馈
    5. 期望的附加功能
    6. 对现有功能的评价与建议
    7. 对系统的满意程度以及推荐程度
  3. 问卷元信息设置:

    1. 社会期望效应:匿名
    2. 顺序效应,锚定效应:5份题目不同顺序问卷
    3. 选择过载效应:限制非自由选项数量最高为5
    4. 工作记忆负荷原则:使用30选择题+1问答题题型
  4. 问卷具体设置与发布:

    工具:问卷星

    以不定向的形式在各种群体中发布,以增加数据的普遍性。另外采用金钱激励的方式对回答问卷的行为给予激励,从而获得更多的数据。

image.png

image.png

  1. 数据收集:

    问卷星能将问卷数据以xlsx的结构化格式给出

  2. 数据清洗与合并:

    由于设计了五份相同题目内容,不同题目顺序的问卷,因此要进行数据清洗与数据合并

    1. 去除题目号码
      def remove_decimal_prefix(column_name):
          return re.sub(r'^\d+、', '', column_name)
      
    2. 利用python pandas库定义输入与输出的dataFrame
      df1 = pd.read_excel('output/test1.xlsx')
      df2 = pd.read_excel('output/test2.xlsx')
      df3 = pd.read_excel('output/test3.xlsx')
      df4 = pd.read_excel('output/test4.xlsx')
      df5 = pd.read_excel('output/test5.xlsx')
      
      result_df = pd.DataFrame()
      
    3. 对所有题目去除序号
      new_columns = {col: remove_decimal_prefix(col) for col in df1.columns}
      df1.rename(columns=new_columns, inplace=True)
      new_columns = {col: remove_decimal_prefix(col) for col in df2.columns}
      df2.rename(columns=new_columns, inplace=True)
      new_columns = {col: remove_decimal_prefix(col) for col in df3.columns}
      df3.rename(columns=new_columns, inplace=True)
      new_columns = {col: remove_decimal_prefix(col) for col in df4.columns}
      df4.rename(columns=new_columns, inplace=True)
      new_columns = {col: remove_decimal_prefix(col) for col in df5.columns}
      df5.rename(columns=new_columns, inplace=True)
      
    4. 进行列合并获得最终用于需求可视化的xlsx文件
      merged_df = pd.concat([df1, df2, df3, df4, df5], axis=0)
      
      merged_df.to_excel('output/merge.xlsx', index=False)
      

1.2 市场分析与竞争分析

面向法条的法律文本生成与推荐是我们的主要创新内容,同时也是本系统的目标定位

1. 市场分析

数据来源:

American Bar Association (ABA) 2023 Survey Report.
Thomson Reuters Legal Executive Institute 2023 Report
Corporate Legal Operations Consortium (CLOC) 2023 Report
LegalTech Market Research 2023
Gartner LegalTech User Survey 2023.
International Legal Technology Association (ILTA) 2023 Report

获得结论:

律师事务所的法律文书撰写工作占据律师日常工作的30%-50%
美国大型律师事务所的平均年运营成本约为200万美元,自动化工具的应用可节省约40万美元。
超过70%的客户对法律服务的响应速度和文书质量有较高期待
自动化减少文书合规风险,预计可降低企业合规性罚款风险的40%。
全球法律服务市场规模在2023年已达1.2万亿美元,预计到2028年将达到1.8万亿美元,年均增长率为8%。
在法律服务市场中,法律技术市场规模预计从2023年的150亿美元增长到2028年的350亿美元,年均增长率超过15%。其中,自动化和智能化文书生成工具的市场需求将占据法律技术市场的20%以上
在线合同生成、诉讼文书自动撰写、法律意见书生成等的引入可增加公司收入的15%-25%。
......

结论:法律文书生成系统市场前景良好

2. 竞争分析

1. 北大法宝来签智能合同生成系统

image.png

产品功能:

image.png

image.png

合同生成:

模板库
智能填写
个性化定制

法律文书生成:

诉讼文书,法律意见书等
法规引用
智能文本生成
自动校对

知识图谱:

法律知识图谱
实时更新

多人协作:

协同编辑
权限管理
文书管理
版本控制
云端存储

数据加密:

传输加密,SSL/TLS加密技术
存储加密
隐私保护政策
细粒度访问控制

合同知识库

庞大数据,法宝来签基于北大法宝法律数据库
支持用户自定义标签,辅助用户对系统文本内容添加私有化属性

优缺点分析

优点:高效智能,专业规范,协作便捷,数据安全,规则引擎
缺点:在个性化定制和复杂文书生成方面存在不足,较高的价格对于普通人负担过重

image.png

image.png

2. 一码千言

image.png

界面功能:

搜索栏
分类标签
类别区分
点赞和评论
收藏功能

智能化功能:

模板使用
用户反馈
个性化推荐
自动更新

优缺点分析

优点:高效智能,专业规范,用户体验,协作便捷
缺点:没有给出智能法条推荐;模板数量较少,生成速度较慢

image.png

image.png

2. 当前模型目标功能的分析:输入对应的案情信息,将对应相关的法条展示给用户,当用户需要生成对应格式的文书时,我们要接收用户生成的相关法条以及其他相关信息来生成对应的法律文书

1.3 需求跟踪

需求追踪文档

项目名称:法律文书生成系统
文档版本:1.3
初始创建日期:2024-04-25
需求编号需求描述优先级状态负责人完成日期备注
R001能够快速生成高质量的法律文书已完成dkh2024-05-25
R002寻找丰富的法律文书模板库,包括合同、诉讼文书、法律意见书等已完成qyk2024-04-28需定期更新模板(上次更新:2024/5/25)
R003系统支持个性化定制和修改生成的文书进行中dqh(预计)2024-07-15
R006系统提供数据加密和隐私保护措施(随机密钥+对密钥的加密)已完成lzy2024-05-25
R008支持用户搜索和筛选所需的法律文书模板进行中dkh(预计)2024-06-20
R009使用NodeJS建立后端服务器已完成lzy2024-05-15
R010系统自动校对生成的文书,提示语法错误、逻辑错误和法律风险待开始dqh(预计)2024-07-1
R011丰富的法律案例库获取已完成qyk2024-04-29需定期更新模板(上次更新:2024/5/25)
R012界面排版设计已完成ys2024-04-29
R013移动端兼容设计进行中qyk(预计)2024-06-15
R014交互设计进行中qyk(预计)2024-06-20
R015模型调用接口设计进行中qyk, lzy(预计)2024-06-10
R016可视化实现待开始qyk(预计)2024-06-15
R017需求收集已完成lwl2024-04-30
R018基于市场与竞争的需求分析已完成lwl2024-04-25
R019需求跟踪进行中lwl持续进行

需求变更文档

项目名称:法律文书生成系统
文档版本:1.2
创建日期:2024-04-30

定义需求变更

变更请求提交
变更影响分析
变更审批
变更实施
变更监控

变更编号变更日期变更描述变更原因变更影响分析批准状态批准人批准日期
C0012024-05-02系统提供数据加密和隐私保护措施法规要求需修改开发计划,增加安全性功能已批准lwl2024-05-04
C0022024-05-10数据集扩充定时变更要求增加数据收集和处理工作量已批准lwl2024-05-11

变更编号:C001

变更日期: 2024-05-02
变更描述: 系统提供数据加密和隐私保护措施。
变更原因: 用户需求与安全性要求。
变更影响分析:
    需修改开发计划,增加安全性功能。
    可能延长开发时间。
    增加开发和维护成本。
    需要对开发团队进行额外培训,确保数据加密和隐私保护措施的正确实施。
批准状态: 已批准
批准人: lwl
批准日期: 2024-05-04

变更编号:C002

变更日期: 2024-05-10
变更描述: 数据集扩充
变更原因: 提高系统的覆盖面和实用性,满足更多法律文书生成需求
变更影响分析:
    增加数据收集和处理工作量。
    需要新增数据源和处理流程。
    可能需要增加存储和计算资源。
    需进行数据清洗和格式化,确保数据质量。
批准状态: 已批准
批准人: lwl
批准日期: 2024-05-11

二、前端功能

2.1 前端设计

1、相关法条生成界面:

image.png

如图中的信息所示,相关法条生成需要输入案情信息,这些案情信息可以为诉讼书的案情信息,可以为案情的原告的陈述等等,输出相关法条。

转到对应文书则是考虑到到用户可能需要,但是我们的法律文书生成系统比较小而导致的无法完成用户需求的问题,我们可以转到中华人民共和国最高人民法院官网等等可以直接提供官方文书格式的网站。

转到文书生成是考虑到用户可能会有先看看相关法条,然后再去生成对应文书的需求,因此就添加了这个设想,希望可以直接添加到文书生成中。

修改则是考虑到用户会有增加,减少所需要的法条的需求,尽可能的对于用户有利。

2、法律文书生成

image.png

此处仅以最具代表性的自然人诉讼主体的民事起诉状为例展示法律文书生成系统的界面简单设计

输入是用户的一些具体信息,可以直接添加到文书上

输出则是对应的法律文书

打印是转成对应格式提取文件

3、按钮功能简单示意

在上述的信息中,我的设想简单的使用墨刀进行了初步的规划,可以看看几个不使用后端就可以进行的操作

首先是转到对应文件 image.png 这个是可以直接转到对应网站的设想,我暂时把他设定为中华人民共和国最高人民法院官网,也可以是北大法宝等具有参考价值的网站。

image.png

直接编写事件,出发后转到对应页面

2.2 前端具体实现

1. 对话主页面初步开发

(1) 实现对话框(不同角色)

(2) 实现底部输入框,及输入框上方功能键

(3) 实现发送按钮

2 、对话主页面细节开发

(1)反馈窗口:

image.png

(2)字符流输出

image.png

对于vue的部分组件的详细使用内容:

安装和引入组件库

首先,你需要安装并引入你想要的 UI 框架或组件库。例如,如果你使用的是 Element UI,你可以通过 npm 安装它:

npm install element-ui --save 然后在 Vue 项目中引入并注册 Element UI

简单使用

  <div>  
    <el-component :prop1="value1" @event1="handleEvent"></el-component>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      value1: 'some value',  
    };  
  },  
  methods: {  
    handleEvent() {  
      console.log('Event 1 was triggered!');  
    },  
  },  
};  
</script>

详细组件使用:

el-header

官网el-header默认高度就是为60px;使用下面的方式来对于el-header进行样式设计

        position: relative;
        width: 100%;
        height: 50px;      
    }
    .el-aside {
  display: block;
  position: absolute;
  left: 0;
  top: 50px;
  bottom: 0;
  }
  .el-main {  
    padding: 20px;  
    background-color: #f5f7fa;  
}

当然在vue组件中也可以使用style进行部分样式的定义:

    <el-container>  
        <el-main class="my-custom-main">  
        </el-main>  
    </el-container>  
</template>  
  
<style scoped>  
.my-custom-main {  
    padding: 20px;  
    background-color: #f5f7fa;  
}  
</style>

传递属性和监听事件:

在上面的例子中,:prop1="value1" 是将 Vue 组件的 data 中的 value1 传递给 el-component 组件的 prop1 属性。

@event1="handleEvent" 是监听 el-component 组件触发的 event1 事件,并在事件触发时调用 handleEvent 方法。

查阅组件文档:

对于具体的 "el-component",你应该查阅该组件所属框架的官方文档,以了解如何正确使用它,包括它的属性、事件、插槽等。

总之,Vue 组件的使用涉及到安装和引入组件库、在 Vue 组件中使用组件、传递属性和监听事件等步骤。

前端访问后端接口:

使用原生JavaScript的fetch API

fetch是一个现代的、基于Promise的HTTP客户端,用于浏览器和Node.js中。

  method: 'GET', // 或者 'POST', 'PUT', 'DELETE' 等  
  headers: {  
    'Content-Type': 'application/json', 
  },  
  body: JSON.stringify(data), 
})  
.then(response => response.json())  
.then(data => console.log(data))  
.catch((error) => {  
  console.error('Error:', error);  
});

在vue中使用是这样的:

  <!-- ... 模板内容 ... -->  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      data: null, // 初始化数据为空  
    };  
  },  
  methods: {  
    fetchData() {  
      fetch('https://api.example.com/data')  
        .then(response => {  
          if (!response.ok) {  
            throw new Error('Failed to fetch data');  
          }  
          return response.json(); // 解析 JSON 响应  
        })  
        .then(data => {  
          this.data = data; // 将解析后的数据保存到组件的 data 中  
        })  
        .catch(error => {  
          console.error('Error fetching data:', error); // 处理错误  
        });  
    },  
  },  
};  
</script>
使用axios库

axios是一个基于Promise的HTTP客户端,可以在浏览器和node.js中使用。它提供了许多有用的特性,如拦截请求和响应、转换请求和响应数据等。

首先,需要安装axios(如果你正在使用npm或yarn):

npm install axios  
# 或者  
yarn add axios

然后在你的代码中使用它:

import axios from 'axios';  
  
axios.get('https://api.example.com/data')  
  .then(function (response) {  
    console.log(response.data);  
  })  
  .catch(function (error) {  
    console.log(error);  
  });

而在vue中使用是这样的:

<template>  
  <div>  
    <button @click="fetchData">获取数据</button>  
    <div v-if="data">  
      <!-- 显示数据 -->  
      <p>{{ data.message }}</p>  
    </div>  
  </div>  
</template>  
  
<script>  
import axios from 'axios';  
  
export default {  
  data() {  
    return {  
      data: null, // 初始化数据为空  
    };  
  },  
  methods: {  
    fetchData() {  
      axios.get('https://api.example.com/data')  
        .then(response => {  
          this.data = response.data; // 将响应数据保存到组件的 data 中  
        })  
        .catch(error => {  
          console.error(error); // 处理错误  
        });  
    },  
  },  
};  
</script>

注意事项:

跨域问题:如果你的前端和后端不在同一个域或端口上运行,你将面临跨域资源共享(CORS)问题。你需要确保后端服务器已配置适当的 CORS 策略来允许前端进行跨域请求。 错误处理:始终处理 HTTP 请求中的错误。在上面的示例中,我们使用了 catch 块来捕获并处理错误。 安全性:确保你的 API 请求是安全的。使用 HTTPS 来加密你的请求,并考虑使用身份验证和授权机制(如 OAuth、JWT 等)。 请求和响应拦截:如果你使用的是 axios,你可以使用请求和响应拦截器来在请求发送到服务器之前或响应返回给前端之前进行额外的处理。 状态管理:如果你的 Vue 应用有多个组件需要共享后端数据,考虑使用 Vuex 或其他状态管理库来管理这些数据。

异步(Asynchronous)与同步(Synchronous):

异步:代码的执行不按照顺序,当遇到异步操作时(如网络请求、定时器、事件监听等),会先执行其他代码,待异步操作完成后,再执行异步操作的回调函数。

同步:代码按照顺序一行一行地执行,只有当当前代码执行完毕后,才会执行下一行代码。

为什么js需要同步异步?

防止长时间阻塞 如果 JavaScript 只支持同步编程,那么当一个长时间运行的任务(如网络请求)正在执行时,整个页面都会被阻塞,直到该任务完成。这显然是不合理的,因为用户不希望因为一个请求而等待整个页面无响应。因此,JavaScript 提供了异步编程的能力,允许在执行耗时操作时继续执行其他任务。

更好的用户体验 通过异步编程,开发者可以创建响应更快、更流畅的应用程序。用户不需要等待整个页面或应用程序加载完成就可以开始交互,这大大提高了用户体验。

避免回调地狱(Callback Hell): 虽然回调函数是实现异步编程的一种常用方法,但过多的嵌套回调函数会导致代码难以阅读和维护,这被称为“回调地狱”。为了解决这个问题,JavaScript 社区引入了 Promise、async/await 等新的异步编程技术,它们使得异步代码更加简洁和易于理解。

总之,JavaScript 引入异步编程模型是为了更好地适应现代Web开发的需求,提高性能和用户体验,同时避免回调地狱等问题。

如何使用异步?

async 函数 async 函数是一个声明为异步的函数,它总是返回一个Promise。如果在async函数内部使用了return语句,那么返回的值会被Promise.resolve()处理,并作为Promise的解析值。

如果在async函数内部抛出了异常,那么该异常会被Promise.reject()处理,并作为Promise的拒绝原因。

await 表达式 await只能在async函数内部使用。

它会使JavaScript引擎等待一个Promise,然后返回Promise的结果。如果Promise被解析(fulfilled),那么await表达式的结果就是解析值。

如果Promise被拒绝(rejected),那么await表达式会抛出一个异常。

示例

// 假设 fetchData 是一个返回 Promise 的函数  
function fetchData() {  
    return new Promise((resolve, reject) => {  
        setTimeout(() => {  
            resolve('Data fetched!');  
        }, 1000);  
    });  
}  
  
// 使用 async/await  
async function processData() {  
    try {  
        // 等待 fetchData 返回的 Promise 解析  
        const data = await fetchData();  
        console.log(data); // 输出 "Data fetched!"  
    } catch (error) {  
        console.error('Error fetching data:', error);  
    }  
}  
  
processData(); // 调用 processData 函数

在上面的示例中,虽然fetchData是一个异步操作,但是通过await,我们在processData函数中等待它完成,然后再继续执行后续的代码。这使得代码看起来和写起来更像同步代码,但实际上它仍然是异步的。

vue动态渲染

在 Vue.js 中,动态渲染通常指的是根据数据的变化来动态地更新 DOM。Vue 通过其响应式系统来实现这一点,它允许你声明式地将数据绑定到 DOM 上,并且当数据变化时,DOM 会自动更新。

插值表达式

它是vue框架提供的一种在html模板中绑定数据的方式,使用{{变量名}}方式绑定Vue实例中data中的数据变量。会将绑定的数据实时的显示出来。

示例如下:

<template>  
  <div>  
    <p>{{ message }}</p>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      message: 'Hello, Vue!'  
    };  
  }  
};  
</script>

常用指令

v-bind 指令:

用于绑定属性或样式到表达式。

<template>  
  <div>  
    <img v-bind:src="imageSrc" alt="Vue Logo">  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      imageSrc: 'https://vuejs.org/images/logo.png'  
    };  
  }  
};  
</script>

亦可简写为: <img :src="imageSrc" alt="Vue Logo">

v-for 指令:

用于基于源数据多次渲染元素或模板块。

当使用 v-for 时,务必提供一个 :key 绑定。这有助于 Vue 跟踪每个节点的身份,从而重用和重新排序现有元素。对于数组,通常使用数组的索引作为键(如果列表不会重新排序或过滤),但对于对象,最好使用唯一且稳定的键(如 ID)。

下面是使用v-for遍历对象的例子:

<template>  
  <div>  
    <ul>  
      <li v-for="(value, key) in object" :key="key">  
        {{ key }}: {{ value }}  
      </li>  
    </ul>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      object: {  
        name: 'Vue',  
        version: '2.x'  
      }  
    };  
  }  
};  
</script>
v-if、v-else-if、v-else 指令:

用于根据条件渲染块。

<template>  
  <div>  
    <p v-if="showMessage">Message is shown</p>  
    <p v-else>Message is not shown</p>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      showMessage: true  
    };  
  }  
};  
</script>
v-show 指令:

根据条件来切换元素的 CSS display 属性。

<template>  
  <div>  
    <button @click="toggleShow">Toggle Show</button>  
    <p v-show="isShown">This paragraph will be toggled based on isShown.</p>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      isShown: true  
    };  
  },  
  methods: {  
    toggleShow() {  
      this.isShown = !this.isShown;  
    }  
  }  
};  
</script>

在上面的示例中,当 isShown 为 true 时,

元素会被显示;当 isShown 为 false 时,

元素会被隐藏(通过设置 display: none)。点击按钮会切换 isShown 的值,从而切换

元素的显示状态。

vue定位

通过组合这些 Vue 的特性和指令,你可以创建出复杂且响应式的用户界面。

定位(Positioning)通常指的是如何使用 CSS 来控制元素在页面上的位置。Vue 本身并不直接提供定位功能,但你可以通过绑定样式(使用 :style 或 :class)或计算属性来控制元素的 CSS 属性,从而实现定位。

以下是一些常见的 CSS 定位属性和如何在 Vue 中使用它们:

静态定位 (Static Positioning):

这是元素的默认值。元素按照正常文档流进行定位。在 Vue 中,你不需要特别处理静态定位。

相对定位 (Relative Positioning):

元素相对于其正常位置进行定位。因此,"left:20px" 会向元素的 LEFT 位置添加 20 像素。

示例如下:

    <template>  
  <div class="container">  
    <div :style="{ position: 'relative', left: '20px', top: '30px' }">  
      I'm positioned relatively!  
    </div>  
  </div>  
</template>

绝对定位 (Absolute Positioning):

元素相对于最近的已定位祖先(而不是正常流中的祖先)进行定位。如果没有已定位的祖先元素,那么它的位置相对于初始包含块。

    <template>  
  <div class="container" style="position: relative;"> <!-- Container is now positioned -->  
    <div :style="{ position: 'absolute', left: '20px', top: '30px' }">  
      I'm positioned absolutely!  
    </div>  
  </div>  
</template>

固定定位 (Fixed Positioning):

元素相对于浏览器窗口进行定位,即使页面滚动,它也会始终位于同一的位置。

    <template>  
  <div :style="{ position: 'fixed', right: '20px', bottom: '30px' }">  
    I'm fixed to the bottom right corner!  
  </div>  
</template>

粘性定位 (Sticky Positioning):

粘性定位可以被视为相对定位和固定定位的混合。元素在跨越特定阈值之前为相对定位,之后为固定定位。注意,不是所有浏览器都支持粘性定位。

    <template>  
  <div :style="{ position: 'sticky', top: '0', background: 'yellow' }">  
    I'm sticky until I reach the top!  
  </div>  
</template>

使用计算属性或方法来动态设置定位:

你可以使用 Vue 的计算属性或方法来动态地设置元素的定位。例如,你可能有一个计算属性,它根据某些条件返回不同的定位值。

在我们的项目中,vue定位具体的实现如下:

    getLocation() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(this.showPosition, this.showError);
      } else {
        this.location = "无法获取定位信息";
      }
    },
    async showPosition(position) {
      const lat = position.coords.latitude;
      const lon = position.coords.longitude;
      const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=zh`);
      const data = await response.json();
      this.location = `${data.principalSubdivision} ${data.city}`;
    },
    showError(error) {
      switch(error.code) {
        case error.PERMISSION_DENIED:
          this.location = "用户拒绝请求地理定位";
          break;
        case error.POSITION_UNAVAILABLE:
          this.location = "位置信息不可用";
          break;
        case error.TIMEOUT:
          this.location = "请求用户地理位置超时";
          break;
        case error.UNKNOWN_ERROR:
          this.location = "未知错误";
          break;
      }
    }
  }

pdf生成

在Vue项目中生成PDF,可以使用多种库来实现这一功能。以下是一些常用的库和方法:

jsPDF

jsPDF 是一个流行的JavaScript库,用于在客户端和服务器端生成PDF文档。但是,jsPDF 主要用于创建简单的文本和图形PDF,而不支持HTML到PDF的转换。

简单的生成示例:

    import { jsPDF } from 'jspdf'  
  
export default {  
  methods: {  
    generatePDF() {  
      const doc = new jsPDF()  
      doc.text('Hello world!', 10, 10)  
      doc.save('sample.pdf')  
    }  
  }  
}

html2canvas + jsPDF

如果需要将HTML内容转换为PDF,可以结合使用html2canvas和jsPDF。html2canvas可以将HTML渲染为canvas,然后可以使用jsPDF的addImage方法将canvas内容添加到PDF中。

使用示例:

    import html2canvas from 'html2canvas' import { jsPDF } from 'jspdf' export default { methods: { async generatePDF() { const element = document.querySelector("#capture") // 要转换为PDF的HTML元素 const canvas = await html2canvas(element) const imgData = canvas.toDataURL('image/png') const pdf = new jsPDF('p', 'mm', 'a4') const width = pdf.internal.pageSize.getWidth() const height = (canvas.height * width) / canvas.width pdf.addImage(imgData, 'PNG', 0, 0, width, height) pdf.save('sample.pdf') } } }

vue-html2pdf

vue-html2pdf是一个基于Vue的包装器,它简化了HTML到PDF的转换过程。它使用html2canvas和jsPDF,但提供了一个更简洁的API。

    import VueHtml2Pdf from 'vue-html2pdf'  
  
Vue.use(VueHtml2Pdf)  
  
export default {  
  methods: {  
    generatePDF() {  
      this.$html2pdf.generatePdf({  
        margin: 1,  
        filename: 'sample.pdf',  
        image: { type: 'jpeg', quality: 0.98 },  
        html2canvas: { scale: 2 },  
        jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' },  
        content: [this.$refs.content], // 要转换为PDF的HTML元素  
      })  
    }  
  }  
}

 我们在此次实践中的写法如下:

     async downloadPdf() {
      const element = this.$refs.appealContent;
      const canvas = await html2canvas(element);
      const imgData = canvas.toDataURL('image/png');
      const pdf = new jsPDF();
      pdf.addImage(imgData, 'PNG', 10, 10);
      pdf.save('appeal.pdf');
    },

基于vue的待填写内容识别与交互式填写

对于预定目标内容的填写,我们使用vue进行规划

步骤:

定义数据模型:首先,你需要在Vue组件的data函数中定义一个数据模型,这个模型将包含你需要填写的内容的初始状态。

渲染表单 双向数据绑定:Vue的核心特性之一是双向数据绑定,这意味着当你更改表单输入时,数据模型会自动更新,反之亦然。你可以使用v-model指令来实现这一点。

待填写内容识别:你可能需要一种方法来识别哪些表单项是待填写的。这可以通过在数据模型中设置默认值(如空字符串或null)来实现。然后,你可以使用Vue的计算属性或方法来检查哪些表单项的值是空的。

交互式填写:一旦你识别了待填写的表单项,你就可以使用Vue的事件处理机制来创建交互式填写功能。例如,你可以监听表单项的change或input事件,并在事件处理函数中更新数据模型。

简单的示例如下所示:

    <template>  
  <div>  
    <form @submit.prevent="submitForm">  
      <div v-for="(value, name) in formData" :key="name">  
        <label>{{ name }}:</label>  
        <input v-model="formData[name]" type="text">  
        <!-- 可以添加更多验证和交互逻辑 -->  
      </div>  
      <button type="submit">提交</button>  
    </form>  
    <div v-if="hasEmptyFields">  
      请注意,还有以下字段需要填写:  
      <ul>  
        <li v-for="(value, name) in emptyFields" :key="name">{{ name }}</li>  
      </ul>  
    </div>  
  </div>  
</template>  
  
<script>  
export default {  
  data() {  
    return {  
      formData: {  
        name: '',  
        email: '',  
        // 添加更多字段...  
      },  
    };  
  },  
  computed: {  
    emptyFields() {  
      return Object.entries(this.formData)  
        .filter(([_, value]) => !value)  
        .map(([name]) => name);  
    },  
    hasEmptyFields() {  
      return this.emptyFields.length > 0;  
    },  
  },  
  methods: {  
    submitForm() {  
      // 提交表单逻辑...  
      console.log(this.formData);  
    },  
  },  
};  
</script>

具体实现:

    <template>
  <div id="container">
    <div class="content">
      <div class="detail_title">
        <div class="detail_medtitle"></div>
        <div class="detail_bigtitle">民事反诉状(法人或者其他组织提起民事反诉用)</div>
        <div class="detail_halftitle"></div>
      </div>
<div>  XXXXXXXX   </div>
</div>
</div>
</template>

使用上述的表达方式进行填写内容即可将文书模板的DOM树进行简单的生成

本次项目实训我的另一项任务就是对于各项素材的寻找,例如前端各种样式的寻找,本文主要是记录前端素材的寻找

明确需求

首先,对于本次的项目实训,我们的前端基调为冷色系,黑色,褐色等等(表示严肃的颜色类型)

p11.png

素材来源

我们的素材主要来源于网上的一些有关于法律的图片,具体来源有360图片,百度图片,稿定设计,秀米等等图片库及图片编辑网站

屏幕截图 2024-06-22 201749.png

在上面即可正常对其进行编辑,更改尺寸,编辑色调等等,并对其进行导出

前端参考样式

文本的参考格式是比较多样化的,因此在这里不多讲,这里我们搜集了一定的前端的样式,对其进行了一定的参考分析,以寻找最适合我们的前端界面形式以及前端格式

对于我们的文书生成系统,我们最基本的格式参考了北大法宝的形式,

前端样式对其进行了部分的借鉴,并根据经验进行一定的修正以达成更好的效果

其他素材收集

法律文书系统的主要文书格式来源于中华人民共和国最高人民法院网

法律文书示例

我对于官网的部分文书进行了一定的筛选,将我们认为比较重要的一些文书进行了录入,并构建了比较优雅的前端

屏幕截图 2024-06-23 131814.png 法律参考文件的收集 还有一部分的法律文件需要进行参考的,一般来源于北大法宝,

如相关的法律法规的收集

屏幕截图 2024-06-23 132739.png

相关的法律文件 (曾经的相似案件)

屏幕截图 2024-06-23 132906.png

css样式

引入css文件

<link>元素允许你指定一个外部CSS文件的路径,这样浏览器就可以加载和应用这些样式到HTML元素上了。

<link rel="stylesheet" type="text/css" href="styles.css">

或者使用import来进行修正 @import url("./young.css")

CSS选择器

CSS选择器是用于选择你想要样式化的HTML元素的模式。它们定义了哪种HTML元素将受到CSS样式的影响。

常见的css选择器有以下几种:

元素选择器

选择所有指定类型的html元素

    p {  
    color: green;  
}

选定所有的标签为p的元素,将它们变成绿色

ID选择器

选择指定ID的HTML元素

    #myID {  
    background-color: yellow;  
}

把ID为myID的元素选定,并将背景颜色变成黄色

属性选择器

选择带有指定属性和值的HTML元素

    a[target="_blank"] {  
    text-decoration: none;  
}

选择所有target属性值为"_blank"的元素并移除它们的下划线。

类选择器

选择所有带有指定类名的HTML元素

    .myClass {  
    font-size: 20px;  
}

选择所有带有class="myClass"属性的HTML元素并将它们的字体大小设置为20像素。

css初始化

在CSS中,初始化(也称为重置或标准化)是一个常见的做法,用于消除不同浏览器之间的默认样式差异,确保页面在不同浏览器上看起来尽可能一致。这通常涉及到设置一些常见的HTML元素的默认样式为统一的、无样式(或基础样式)的状态。

以下是一个简单的CSS初始化示例

    *,  
*::before,  
*::after {  
  margin: 0;  
  padding: 0;  
  box-sizing: border-box; 
}  
  
ul, ol, dl {  
  list-style: none;  
}  
  
a {  
  text-decoration: none;  
  color: inherit;   
}  
  
table {  
  border-collapse: collapse; 
  border-spacing: 0;
}  
  
body {  
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 
  font-size: 16px;   
  line-height: 1.5; 
  color: #333;   
}  
  
/* 其他可能的初始化... */  
/* 例如,设置输入框样式、按钮样式等 */  

当我们要使用这个css初始化时,可以将其作为一个css文件保存下来,并通过在html的head中引入来使用

css的盒模型

css盒模型(Box Model)是css布局的基础,它决定了元素如何在页面上渲染其尺寸以及与其他元素的空间和位置关系。每个HTML元素都可以看作是由内容、内边距(padding)、边框(border)和外边距(margin)组成的矩形盒子。

css盒模型的组成主要有以下几点:

内容

元素的实际内容,比如文本、图片等。

内容的大小可以通过 width 和 height 属性设置。

内边距

内边距是内容与其边框之间的空间。

内边距的大小可以通过 padding 属性设置,可以分别设置上、右、下、左四个方向的内边距,或者设置统一的内边距。

内边距是透明的,且会占用布局空间。

边框

边框是紧接内边距的线,包围在内容和内边距外面。

边框的大小、样式和颜色可以通过 border 属性设置。

边框会占用布局空间。

外边距

外边距是边框与其他元素之间的空间。

外边距的大小可以通过 margin 属性设置,同样可以分别设置上、右、下、左四个方向的外边距,或者设置统一的外边距。

外边距是透明的,且会与其他元素的外边距合并(margin collapsing)。

在盒模型中,一个元素的 width 和 height 指的是内容区域的宽度和高度,而不包括内边距、边框和外边距。但是,元素在页面上占用的总空间是其内容、内边距、边框和外边距的总和。

另外,css3引入了box-sizing属性,它允许你改变默认的盒模型计算方式。当box-sizing设置为border-box时,width和height属性将包括内容、内边距和边框,但不包括外边距。这可以使布局更加直观和易于控制。

理解并熟练运用CSS盒模型是前端开发的基础,它可以帮助你更好地控制页面元素的布局和样式。

轮播图

轮播图(也称为图片滑动组件或滑动广告)是一个在网页上非常常见的元素,它允许用户在不离开当前页面的情况下浏览多张图片或内容。

以下是一个简单的轮播图实现的概述,以及使用HTML、CSS和JavaScript(或jQuery)的基本代码示例。

HTML示例

HTML内容用来设置轮播图的内容

    <select name="cars">  
  <option value="volvo">Volvo</option>  
  <option value="saab">Saab</option>  
  <option value="mercedes">Mercedes</option>  
  <option value="audi">Audi</option>  
</select>
CSS示例

我们需要隐藏所有非活动的轮播项,并设置过渡效果以实现平滑的滑动。

.carousel {  
  position: relative;  
  width: 100%;  
  overflow: hidden;  
}  
  
.carousel-item {  
  position: absolute;  
  width: 100%;  
  opacity: 0;  
  transition: opacity 1s ease-in-out;  
}  
  
.carousel-item.active {  
  opacity: 1;  
}  
  
.carousel img {  
  width: 100%;  
  height: auto;  
}
JS示例

使用JavaScript(或jQuery)来处理轮播图的逻辑。这包括定期更改活动轮播项,以及可能还包括添加导航按钮或指示器。

let currentIndex = 0;  
const items = document.querySelectorAll('.carousel-item');  
const itemCount = items.length;  
  
function changeSlide() {  
  // 移除当前活动项的'active'类  
  items[currentIndex].classList.remove('active');  
    
  // 计算下一个活动项的索引  
  currentIndex = (currentIndex + 1) % itemCount;  
    
  // 添加'active'类到下一个活动项  
  items[currentIndex].classList.add('active');  
}  
  
// 每3秒更改一次轮播项  
setInterval(changeSlide, 3000);

选择框

选择框(通常称为下拉选择框或<select>元素)是HTML中用于让用户从多个选项中选择一个选项的输入控件。

HTML示例
  <select name="cars">  
  <option value="volvo">Volvo</option>  
  <option value="saab">Saab</option>  
  <option value="mercedes">Mercedes</option>  
  <option value="audi">Audi</option>  
</select>

在这个例子中,<select>元素定义了一个选择框,而<option>元素则定义了用户可以选择的各个选项。每个<option>都有一个value属性,它表示当该选项被选中时提交给服务器的数据值。而选项的文本内容(如"Volvo"、"Saab"等)则是用户在选择框中看到的。

    // 获取选择框的当前选中值  
var selectedValue = document.querySelector('select[name="cars"]').value;  
console.log(selectedValue); // 输出选中的value值,例如:"volvo"  
  
// 设置默认选项为"Audi"  
var selectElement = document.querySelector('select[name="cars"]');  
for (var i = 0; i < selectElement.options.length; i++) {  
    if (selectElement.options[i].text === "Audi") {  
        selectElement.selectedIndex = i;  
        break;  
    }  
}

前端图片上传

在前端实现图片上传功能,通常涉及到HTML、CSS和JavaScript(或jQuery等库)的结合使用。简单的步骤如下所示:

创建HTML表单

使用

元素创建一个表单,并设置enctype属性为multipart/form-data,以便能够上传文件。在表单内,使用元素创建一个文件选择字段。

    <!DOCTYPE html>  
<html lang="en">  
<head>  
<meta charset="UTF-8">  
<meta name="viewport" content="width=device-width, initial-scale=1.0">  
<title>图片上传示例</title>  
</head>  
<body>  
  
<form id="uploadForm" enctype="multipart/form-data">  
    <input type="file" id="imageFile" name="imageFile" accept="image/*">  
    <button type="button" onclick="uploadImage()">上传图片</button>  
</form>  
  
<script src="upload.js"></script>  
  
</body>  
</html>
编写JavaScript代码

使用JavaScript(或jQuery)来处理文件选择、验证(如果需要)和上传。可以使用FormData对象来构建要发送的数据,并通过XMLHttpRequest或fetch API发送请求到服务器。

    function uploadImage() {  
    var input = document.getElementById('imageFile');  
    var file = input.files[0];  
    var formData = new FormData();  
  
    formData.append('imageFile', file);  
  
    var xhr = new XMLHttpRequest();  
    xhr.open('POST', '/upload-url', true); 
    xhr.upload.onprogress = function(e) {  
        if (e.lengthComputable) {  
            var percentComplete = (e.loaded / e.total) * 100;  
            console.log(percentComplete + '% 上传完成.');  
        }  
    };  
  
    xhr.onload = function() {  
        if (this.status === 200) {  
            alert('图片上传成功!');  
        } else {  
            alert('图片上传失败!');  
        }  
    };  
  
    xhr.send(formData);  
}
服务器端处理

在服务器端(如Node.js、PHP、Python等)编写代码来处理上传的文件,并将其保存到服务器上的某个位置。

页面中的子路由的设计

设计页面子路由对于构建复杂的单页面应用(SPA)至关重要,子路由允许将应用划分为多个视图层次,每个层次都可以有自己的路由逻辑和组件结构。

设计的步骤如下所示:

定义路由结构->使用路由库->设计组件结构->传递参数和状态->导航和链接->路由守卫和重定向->测试和调试->响应式设计

项目实训个人管理员端设计

管理员实现的功能:登录、文书管理、文书类型管理、用户管理、点赞管理、评论管理、公告管理、角色管理、菜单管理和轮播图管理。这个东西写过信息交互系统的应该都知道,这里就不加赘述,只直观展示如何实现的。

1、登录

b6b822cb158221759c81d155d6989913_fe905170650a4107b5ba185dd2d023b0.png

        <div class="login_box" style="margin: 150px auto; background-color: #fff; width: 500px; height: 400px; padding: 20px; border-radius: 10px">
      <div class="fancy-font"  style="margin: 5px 0; text-align: left; font-size: 50px;color: #FF9933"><b>登 录</b></div>
 
      <el-form :model="user"  :rules="rules" ref="userForm" class="transparent-form">
        <el-form-item prop="username">
          <el-input style="opacity: 0.6; background-color: rgba(255, 255, 255, 0.5);" size="medium" prefix-icon="el-icon-user" v-model="user.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input style="opacity: 0.6; background-color: rgba(255, 255, 255, 0.5);" size="medium" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
        </el-form-item>
        <!--        <el-form-item>-->
        <!--          <div style="display: flex">-->
        <!--            <el-input size="mid" v-model="code" style="width: 200px"></el-input>-->
        <!--            <span @click="refreshCode" style="cursor: pointer; flex: 1;">-->
        <!--              <Identify :identifyCode="identifyCode"></Identify>-->
        <!--           </span>-->
        <!--          </div>-->
        <!--        </el-form-item>-->
 
        <el-form-item style="margin: 10px 0; text-align: left">
          <el-button type="warning" size="small"  autocomplete="off" @click="$router.push('/register')">前往注册</el-button>
          <el-button type="primary" size="small"  autocomplete="off" @click="login">登录</el-button>
        </el-form-item>
        <el-form-item style="margin: 10px 0; text-align: left">
          <el-button type="text" size="mid"  autocomplete="off" @click="handlePass">找回密码</el-button>
        </el-form-item>
      </el-form>
    </div>
        login() {
      // if (this.code !== this.identifyCode.toLowerCase()) {
      //   this.$message({
      //     type: "error",
      //     message: "验证码错误"
      //   })
      //   return;
      // }
      this.$refs['userForm'].validate((valid) => {
        if (valid) {  // 表单校验合法
          this.request.post("/user/login", this.user).then(res => {
            if(res.code === '200') {
              localStorage.setItem("user", JSON.stringify(res.data))  // 存储用户信息到浏览器
              localStorage.setItem("menus", JSON.stringify(res.data.menus))  // 存储用户信息到浏览器
 
              // 动态设置当前用户的路由
              setRoutes()
              if (res.data.role === 'ROLE_USER') {
                this.$router.push("/front/home")
                this.$message.success("登录成功")
              } else {
                this.$router.push("/")
                this.$message.success("登录成功")
              }
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      });
    },

2、文书管理

6591160e743e8632529e9e6e437f1f57_991ab612ab2d4691946c99f0fdc8d9e7.png 文书管理界面算是一个比较具有代表性的页面,首先使用了Element组件对于顶栏,侧边栏进行设计

        <el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'"  @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55"></el-table-column>
      <el-table-column prop="id" label="ID" width="80" sortable></el-table-column>
      <el-table-column prop="name" label="标题"></el-table-column>
      <el-table-column prop="content" label="内容">
        <template v-slot="scope">
          <el-button type="primary" @click="view(scope.row.content)">查看内容</el-button>
        </template>
      </el-table-column>
      <el-table-column prop="user" label="发布人"></el-table-column>
      <el-table-column prop="time" label="发布时间" width="150"></el-table-column>
      <el-table-column label="图片" width="140"><template slot-scope="scope"><el-image style="width: 100px; height: 100px" :src="scope.row.img" :preview-src-list="[scope.row.img]"></el-image></template></el-table-column>
      <el-table-column prop="blogType" label="文书类别"></el-table-column>
      <el-table-column prop="reviewer" label="审核人"></el-table-column>
      <el-table-column prop="state" label="审核状态"></el-table-column>
      <el-table-column prop="reason" label="审核描述"></el-table-column>
      <el-table-column label="操作"  width="280" align="center">
        <template slot-scope="scope">
          <el-button type="success" @click="handleEdit2(scope.row)" v-if="scope.row.state === '待审核'">审 核 <i class="el-icon-edit"></i></el-button>
 
          <el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
          <el-popconfirm
              class="ml-5"
              confirm-button-text='确定'
              cancel-button-text='我再想想'
              icon="el-icon-info"
              icon-color="red"
              title="您确定删除吗?"
              @confirm="del(scope.row.id)"
          >
            <el-button type="danger" slot="reference">删除 <i class="el-icon-remove-outline"></i></el-button>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

本段代码主要是对于页面的内容的设计,里面的内容基本都是使用Element UI组件进行设计的,

3、文书类型管理

646b9af3bcc730e974be08c7326ce88a_1c8e550468994d25a4d758dedd107c96.png

354eecb2c454d36e33f9e72c0e9b59af_a25a8c274ecf4e8aa83c53ccd5e04eb2.png 保存新文书类型

        save() {
      this.form.content = editor.txt.html()
      this.$refs['ruleForm'].validate((valid) => {
        if (valid) {
          this.request.post("/blog", this.form).then(res => {
            if (res.code === '200') {
              this.$message.success("保存成功")
              this.dialogFormVisible = false
              this.dialogFormVisible2 = false
              this.load()
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      })
    },

4、用户管理

90ee876ddef8958c695a71f91d106b7d_0df4929a554b41a384988a811d8d2db0.png

5、公告管理

b8899b586c109db3124b68e1b5efe6e1_4de5f9567cfd4ca7ba31bed1eec62c48.png

6、角色管理

b7f05c41c0e5f7b43a0e1d12cf5be1fc_d0ff7ac9c2624462be4083b6699e3532.png

7、菜单管理和轮播图管理

13e9f76fd7d2fbb3d2d38b4a1c0e7c49_7552ac486435478b938b79fbe076db59.png

54b6105ff3fe96646cdaf835a9dc6218_bfdd9ae0a5334db49f5a3aaa3511a721.png

fac950d9c9e58f56fc80df768c567d8d_bbb1a57afbb147dca8b9525403fe574e.png

    <div class="lunbotu" style="margin: 10px 0">
      <el-carousel height="450px" :interval="10000">
        <el-carousel-item v-for="(item,index) in files" v-if="index < 5" :key="item.id">
          <el-image
              style="width: 100%; height: 100%"
              :src="item.url"
              :fit="fit"></el-image>
        </el-carousel-item>
      </el-carousel>
    </div>

轮播图的实现主要是使用了Element UI的组件。v-for是对于item对象的多个元素进行渲染,v-if是对数量进行限制,就是说大于五个时只有前五个会被轮播图播放,小于五个没有任何问题。src="item.url" 表示图片的来源地址是从当前遍历到的 item 对象中的 url 属性获取的。

界面设计

本文旨在分享前端设计的基本思路。

    一个合理有效的前端页面需要遵循以下原则:

            1.完整性:页面需要实现完整的功能

            2.协调性:页面需要减少高对比的元素混杂。

            3.针对性:页面需要直观地展示希望向用户的信息

功能分析:

登录、看文书、发文书、生成文书、问题咨询

前端页面

1.登录,在尽可能不影响罗老师帅气的脸庞的同时,实现我们的登录功能,用户可以在这里进行注册的行为,以实现发布文书的特殊功能,如果没有发布文书的需求就可以直接访问链接前往用户主页。在设计上选择对比度不高的蓝白底色,让用户有眼前一亮的感觉。

71d3b77cd09197f415f0e9022de6f086_398a4465bc474d76a78eba3584ba57c7.png

2.浏览主页,主页的设计专注于介绍一些法律相关内容的大轮播图,并在公告出展示一些有趣的法律新闻,鼠标悬停还能查看公告的细节,用户在浏览系统时,可以了解到一些管理员发布的新闻。管理员也可以通过公告和轮播图发布一些希望用户知道的内容。

52500a977a194f8151e7c51d991d8329_209228bf562a426db2e06b369d98d410.png

6227c92a742f66a49a5a4539cee2ae93_a8fa373493624b649e7d5bd3821abe05.png

3.浏览文书:系统中提供四种不同类型的文书,管理员可以根据需求添加不同的新的类型的文书,用户可以点击不同的标签选择浏览自己喜欢的文书,也可以点击文书进入详情页,阅读更多文书的细节。

057dcf04ebebd5dbc4fb225542863e0d_810f20d4a9f64b5aaf6bebd0242bb510.png

92bdd1622039de4eb4aff4b3854b9707_59cfd55945b2415d8064b2260c6b04f4.png

4.发布文书:发布文书选择清爽的背景,实现类型选择,文书标题,实现图片的导入,并提供一个大文本框,以满足文本编辑的各种类型的需求。

ef728ab5be41dc7a1d21611d3639d39d_f3f0e63a499c4b4f8d02aa65b4ce2392.png

5.模板使用:用户可以在模板使用端点击不同的模板,实现文书的生成

90c129b0fb2531dc0738ae1f9628edb4_f440d64252d04b96ae342cb0e5fd5bac.png

6.问答设计:通过问答界面,用户可以询问模型与法律相关的问题,模型会给出它的参考回答,回答采用Markdown结构,实现回答重点的突出。

110a48e11f96ac31c2a8d7c47ef40ec5_9740da81aac545879ea8cfe74d65d8a4.png

ed81be2cfdf1acf1328f979078ab7b7c_579162fd0d07498f9d0fb47a1286844a.png 系统核心图表的使用:

 公告表

具体图表展示:

e08a321e5d5f7b713cffa4e1a99ef120_9afae844db1a44548d48a5231b41741a.png

        <el-card class="fancy-font" style="width: 1200px;  margin: 10px auto">
      <h2 style="margin: 20px 0">公告列表</h2>
      <el-collapse accordion  v-model="active">
        <el-collapse-item v-for="(item,index) in notices" :key="item.id" :name="'' + index">
          <template slot="title">
            <span style="font-size: 20px;">{{ item.name }}</span>
            <span style="margin-left: 10px">{{ item.time }}</span>
          </template>
          <div>{{ item.content }}</div>
        </el-collapse-item>
      </el-collapse>
    </el-card>

样式:

    .fancy-font {
  font-family: 'Pacifico', cursive; /* 使用 'Pacifico' 字体,如果字体加载失败则使用默认的手写字体 */
  color: #330000; /* 设置字体颜色 */
  font-size: 20px;
  opacity: 0.8;
  font-weight: bold;
}

文书表

4a602772cf09e179ed12dd9c6f184f55_87ec921b2f0445c2b7b7877b84e094ce.png

        <el-row>
      <el-col :span="3">
        
      </el-col>
      <el-col :span="20">
        
       </el-col>
     </el-row>

文书类型的展示与获取:

展示:

         <div style="margin-top: 20px;">
          <el-tabs v-model="type" @tab-click="handleClickType"  tab-position="left">
            <el-tab-pane disabled>
              <span slot="label" style="font-size: 18px;color: #8c939d;font-family: 'Great Vibes', cursive; font-weight: bold;">类型</span>
            </el-tab-pane>
            <el-tab-pane style="font-family: 'Great Vibes', cursive; " v-for="item in types" :label="item.name"></el-tab-pane>
          </el-tabs>
        </div>

获取:

        load() {
      console.log(this.type)
      this.request.get("/blog/page/type", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
          type: this.type
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
 
      this.request.get("/blogType").then(res => {
        this.types = res.data
      })
    },

点击类型时触发分类检索功能

     handleClickType(tab, event) {
      this.type = this.types[tab.index - 1].name
      this.load();
    },

最终文书展示:分为图片和文书描述内容的流式布局

     <div>
   <el-row :gutter="10" style="margin: 10px 0" v-for="item in tableData" :key="item.id">
            <el-col :span="7" style="margin-bottom: 10px">
              <img :src="item.img" alt="" style="width: 100%;height:200px;opacity: 0.8 ">
            </el-col>
            <el-col :span="16" style="margin: 40px 0 10px 20px">
              <div class="fontCss" style="font-weight: bold" @click="$router.push('/front/blogDetail?id=' + item.id)">
                {{ item.name }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                发布人:{{ item.user }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                发布时间:{{ item.time }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                文书类别:{{ item.blogType }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                浏览量:{{ item.pageviews }}
              </div>

新的点击、刷新会触发页面更新功能,会进一步优化页面展示的文书内容

                      this.request.get("/blog/page/type", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
          type: this.type
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
 
      this.request.get("/blogType").then(res => {
        this.types = res.data
      })

文书搜索结果:因为没有写搜索文书触发时,自动补全文书类型以匹配侧边栏的类型,因此将搜索结果展示在一个新的页面中,显示用户希望检索的文书名字、文书类型。对input组件进行路由绑定,就可以直接跳转;

                            <el-button size="medium" class="ml-5" type="text" @click="$router.push('/front/search?name=' + name2)">搜索</el-button>

搜索结果:以搜索中央法规的为例:

自动拼接搜索路由,并转到搜索结果页面:

http://localhost:8080/front/search?name=%E4%B8%AD%E5%A4%AE%E6%B3%95%E8%A7%84

0c8d27bfbf1f01383d3b542af1e584e4_ff7c8c3179854d798b14f6834eaa6448.png 与文书展示页面相同的流式布局

                 <el-row :gutter="10" style="margin: 10px 0" v-for="item in tableData" :key="item.id">
        <el-col :span="5" style="margin-bottom: 10px">
          <img :src="item.img" alt="" style="width: 100%;height:200px;opacity: 0.8 ">
        </el-col>
        <el-col :span="18" style="margin: 20px 0 10px 20px">
          <div class="fontCss" style="font-weight: bold">
            <a @click="$router.push('/front/blogDetail?id=' + item.id)" href="#">{{ item.name }}</a>
          </div>
          <div  style="margin-top: 10px" class="fontCss">
            发布人:{{ item.user }}
          </div>
          <div style="margin-top: 10px" class="fontCss">
            发布时间:{{ item.time }}
          </div>
          <div style="margin-top: 10px" class="fontCss">
            文书类别:{{ item.blogType }}
          </div>
          <div style="margin-top: 10px" class="fontCss">
            浏览量:{{ item.pageviews }}
          </div>
        </el-col>
      </el-row>

触发函数:

                    load() {
      console.log(this.type)
      this.request.get("/blog/page/search/", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
 
    },

模板文书表:采用流式布局实现模板文书生成选择器。用户可以通过点击相应的标题前往模板生成的页面生成自己需要的法律文书。

2f2c07d3fa8f4b451323d20e7024bbfc_8c64e09271a34cb1b1c2ea55531c6cd5.png

                    <el-row>
      <el-col :span="20">
        <div>
          <el-row :gutter="10" style="margin: 10px 0" v-for="item in tableData" :key="item.id">
            <el-col :span="7" style="margin-bottom: 10px">
              <img :src="item.img" alt="" style="width: 100%;height:200px; ">
            </el-col>
            <el-col :span="16" style="margin: 10px 0 10px 20px">
              <div class="fontCss" style="font-weight: bold;color: #330000;font-size: 24px " @click="$router.push('/front/'+item.type)">
                {{ item.name }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                文书描述:{{ item.user }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                文书类别:{{ item.blogType }}
              </div>
            </el-col>
          </el-row>
 
          <div style="padding: 10px 0">
          </div>
        </div>
      </el-col>
    </el-row>

其中实现点击路由跳转的细节:
<div class="fontCss" style="font-weight: bold;color: #330000;font-size: 24px " @click="$router.push('/front/'+item.type)"> 静态数据设置:

                      tableData: [
        {"id":0,
          "name":"最好的判决书模板",
          "user":"判决书是法院对案件审理后,根据查明和认定的案件事实,依照法律规定,对案件实体问题作出的权威性判定。它标志着案件审理的终结,且判决书一经作出即具有法律效力,非经法定程序不得变更或撤销。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"判决书",
          "type":"PanJueShu",
        },
        {"id":1,
            "name":"最好的不予受理支付令申请通知书(通知申请人不予受理用)模板",
            "user":"主要作用在于通知并告知债权人支付令申请的处理结果、列明不符合的条件、指引后续操作、维护法律秩序、保障当事人权益以及作为法律文件等方面。",
            "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
            "blogType":"不予受理支付令申请通知书(通知申请人不予受理用)",
            "type":"1buYuShouLiTongZhiShu"
        },
        {"id":2,
          "name":"最好的担保书(案外人提供保全或者先予执行担保用)模板",
          "user":"其目的是确保在案件审理过程中,当申请人或被申请人因各种原因无法提供担保时,有案外人能够提供必要的担保,以保障诉讼活动的顺利进行。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"担保书(案外人提供保全或者先予执行担保用)",
          "type":"2danBaoShu"
        },
        {"id":4,
          "name":"最好的民事答辩状(法人或者其他组织对民事起诉提出答辩用)模板",
          "user":"法律赋予处于被告地位的法人或其他组织的一种权利,使其能够针对原告的起诉状内容,提出自己的反驳意见和理由。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事答辩状(法人或者其他组织对民事起诉提出答辩用)",
          "type":"4mingShiDaBianZhuang"
        },
        {"id":5,
          "name":"最好的民事反诉状(法人或者其他组织提起民事反诉用)模板",
          "user":"通过提交反诉状,被告可以主张自己的权益受损,并要求法院对其进行保护。这是被告行使其诉讼权利的一种方式,",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事反诉状(法人或者其他组织提起民事反诉用)",
          "type":"5mingShiFanShuZhuang"
        },
        {"id":6,
          "name":"最好的民事再审申请书(申请再审用)模板",
          "user":"它不仅为当事人提供了一个表达不服与申诉的平台,还是启动再审程序、保障司法公正以及促进法律程序完善的重要工具。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事再审申请书(申请再审用)",
          "type":"6MingShiZaiShenTongZhiShu"
        },
        {"id":7,
          "name":"最好的民事起诉状模板",
          "user":"民事起诉状是当事人向人民法院提起民事诉讼的书面申请。它是启动民事诉讼程序的重要法律文件,用以明确诉讼请求、事实和理由,以及相关的证据和法律依据。提交民事起诉状后,法院将进行审查,如果符合受理条件,将立案并通知被告应诉。随后,法院将组织双方当事人进行证据交换、庭审等诉讼活动,最终作出判决或裁定。\n" +
              "\n" +
              "民事起诉状是维护当事人合法权益的重要手段,应当认真准备,确保内容真实、合法、有效。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事起诉状",
          "type":"7minShiShangShuZhuang"
         },
        {"id":8,
          "name":"最好的行政赔偿调解书(一审行政赔偿案件用)模板",
          "user":"高效解决纠纷,和谐解决争议,保护当事人合法权益,推动法治政府建设,提供法律依据",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"行政赔偿调解书(一审行政赔偿案件用)",
          "type":"8xingZhengPeiChangTiaoJieShu"
        },
        {"id":9,
          "name":"最好的二审应诉通知书(通知被上诉人用)模板",
          "user":"明确告知被上诉人其作为案件当事人,在二审程序中需要履行的诉讼义务。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"二审应诉通知书(通知被上诉人用)",
          "type":"9yingSuTongZhiShu"
        },
        {"id":10,
          "name":"最好的异议书(对管辖权提出异议用)模板",
          "user":"主要用于当事人对法院的管辖权提出异议,并阐述相关理由和依据。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"异议书(对管辖权提出异议用)",
          "type":"10yiYiShu"
        },
        {"id":11,
          "name":"最好的民事上诉状模板",
          "user":"民事上诉状是指当事人对第一审人民法院作出的尚未生效的民事判决或裁定不服,向上一级人民法院提起上诉的书面申请。它是民事诉讼中的一种重要法律文书,用以启动上诉程序。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事上诉状",
          "type":"11shangSuZhuang"
        },
        {"id":12,
          "name":"最好的声明书(社会组织声明无违法记录用)模板",
          "user":"证明社会组织合法性、满足法律程序要求、促进公正审判、加强社会监督、规范社会组织行为等方面具有重要的作用",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"声明书(社会组织声明无违法记录用)",
          "type":"12shengMingShu"
        },
      ],

以判决书为例:

a5cba8a1da037614069e57874bae7594_bac2c641c5a94ce4afd0886096e93e6a.png 点赞历史表:

66091cc9d39d4067489b12ddfa936a7f_f6b15e679408479796d4977c876abaa2.png 以判决书为例:

image.png 点赞历史表:

66091cc9d39d4067489b12ddfa936a7f_f6b15e679408479796d4977c876abaa2.png 根据用户名,检索用户历史点赞的记录,并展示在点赞历史表中

                  <div class="main" style="margin-top: 15px">
    <el-card style="opacity: 0.8">
      <el-table :data="tableData">
        <el-table-column prop="blog.name" label="文书"></el-table-column>
        <el-table-column label="封面"><template slot-scope="scope"><el-image style="width: 100px; height: 100px" :src="scope.row.blog.img" :preview-src-list="[scope.row.blog.img]"></el-image></template></el-table-column>
        <el-table-column prop="userName" label="用户"></el-table-column>
        <el-table-column prop="time" label="点赞时间"></el-table-column>
      </el-table>
      <div style="padding: 10px 0">
        <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="pageNum"
            :page-sizes="[2, 5, 10, 20]"
            :page-size="pageSize"
            layout="total, sizes, prev, pager, next"
            :total="total">
        </el-pagination>
      </div>
    </el-card>
  </div>

数据获取:

                    load() {
      this.request.get("/collect/page", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
    },

法律机器人回答页面设计与实现

写作目的:为积极响应戴鸿君老师对项目实训内容的建设性意见,特写本系列个人纪实,记录团队在项目实训的工作中个人的贡献。

页面展示:

d599967ecbc9257ae1beac1f2f4107b8_9cbbf23b014940be948db1c937afe26c.png 询问我被邻居打断了两条腿,应该做什么才能让邻居付出代价:

cdc787969f313132118f37f7f7696978_134949e27f8942d9ac19e1b3f22bf432.png 界面设计

界面本体嵌入主体的主框架,在顶栏的基础上进行细化的修改。

                <template >
  <div class="main" style="">
      <!-- 对话框组件 -->
      <div style="height: 100vh" v-if="showDialog1" class="dialog-overlay">
        <h2 class="fancy-font" style="text-align: center">用户问题咨询</h2>
        <div class="ask">
          <input style="text-align: center"
                 type="text"
                 id="username"
                 placeholder="请输入您的问题"
                 v-model="question"
          />
          <button @click="submit()" class="submit">询问</button>
        </div>
        <div class="answer" v-html="renderAnswer"></div>
      </div>
    </div>
</template>

在页面调用定义的AIRequest资源

import {AIRequest} from "@/utils/request";

并触发submit方法进行后端AI模型的访问

                    submit() {
      const err = (msg) => `# *${msg}*`
      const question = this.question;
      if (!question) {
        this.answer = err("未输入内容")
        return;
      }
      AIRequest.chat(question,true).then(response =>{
        // 处理响应
        console.log(response);
        if (response.code !=0) {
          this.answer = err("发生错误,请查看服务器日志");
          return;
        }
        this.answer = response.data.answer;
      })
          .catch(error => {
            // 处理错误情况
            console.error(error);
            this.answer = err("发生错误,请查看服务器日志");
 
      })
    },

request.js中申请一个新的资源AIRequest

                const otherURL = "http://localhost:9091/api";
/**
 * @type {{
 *  chat(string): Promise<any>,
 *  ask(string): Promise<any>,
 *  clear(): Promise<any>
 * }}
 */
export const AIRequest = {
    async _send(api, body, isPost) {
        const res = await fetch(`${otherURL}/ai/${api}`,
            isPost ? { method: "post", body: JSON.stringify(body) }
                : { method: "get" });
        if (res.status !== 200)
            return Promise.reject("请求失败");
        return await res.json();
    },
    async ask(input) {
        return await this._send("ask", { input }, true);
    },
    async chat(input) {
        return await this._send("chat", { input }, true);
    },
    async clear(input) {
        return await this._send("clear");
    },
}

对接后端的开放的AI接口

                 
const root = path.dirname(fileURLToPath(import.meta.url));
const defaultPort = 9091;
const tunnel = {
    ssh: {
        host: "yourhost",
        port: port,
        username: "username",
        password: "password"
    },
    port: 12315
};
const third = {
    api: "https://api.moonshot.cn/v1/chat/completions",
    key: "sk-key",
    preset: "preset",
    temperature: 0.5
}
 
export { root, defaultPort, tunnel, third };

Markdown结构输出:

                 <div class="answer" v-html="renderAnswer"></div>
   
 
     import MarkdownIt from "markdown-it";
 
    renderAnswer() {
      return this.md.render(this.answer);
    }
 
 
//样式
.main{
  background-image: url("../../assets/法院相关图片/背景图.jpg");
}
.fancy-font {
  font-family: 'Pacifico', cursive; /* 使用 'Pacifico' 字体,如果字体加载失败则使用默认的手写字体 */
  color: #330000; /* 设置字体颜色 */
  font-size: 40px;
  opacity: 0.8;
  font-weight: bold;
}
.dialog-overlay {
  background-color: white;
  background-image: url("../../assets/法院相关图片/背景图.jpg");
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1em;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
h2 {
  font-size: 2em;
}
.ask {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  width: 80%;
 
}
.ask >>> input {
  width: 85%;
  border-radius: 2em;
}
.ask >>> .submit {
  width: 12%;
}
.submit {
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  font-size: 20px;
  color: white;
  padding: .3em .6em;
  border: 0;
  border-radius: .5em;
  background: dodgerblue;
  overflow: hidden;
  user-select: none;
  box-shadow: 0 .1em .2em #1e1e1e;
  transition: .2s;
}
.submit::after {
  content: "";
  pointer-events: none;
  position: absolute;
  width: 120%;
  padding-top: 120%;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, .5);
  opacity: 0;
  transition: .5s;
}
.submit:hover {
  background: #187ad8;
  box-shadow: 0 .1em .2em .05em #1e1e1e;
}
.submit:active {
  transform: scale(0.96);
  box-shadow: 0 .1em .2em #1e1e1e;
}
.submit:active::after {
  opacity: 1;
  width: 0;
  padding-top: 0;
  transition: 0s;
}
.answer {
  font-family: 'Pacifico', cursive; /* 使用 'Pacifico' 字体,如果字体加载失败则使用默认的手写字体 */
  font-size: 20px;
  margin: .5em 2em;
  padding: 1em;
  background-color: rgba(255, 192, 203, 0.5);
  border-radius: 5px;
  height: 75vh;
  line-height: 1.5em;
  box-shadow: 0 0 5px 1px #0000007f;
  overflow-y: auto;
  min-width: 80%;
}
.answer >>> ol {
  padding-left: 1em;
}
 
.pri {
  transform: translateX(-69vw);
  font-size: 0.3rem;
  color: #187ad8;
  background-color: transparent;
  border: none;
  margin-top: 0.3rem;
}

法律文书发布指南

aef8c320c5641e2c1bacb05182a02d40_8d68da089ee94f82bbae946d27a6ef6c.png 点击框内触发路由跳转函数,实现进入模板文书生成界面:

5f0bba9aa6caa7b4440e9b21d4378acf_2146cdcba1914617abaa1a92c352e610.png

ec692bc7599736f2f5c09190c7a03da4_4f075e976371464f9c450c08cb348b88.png 模板列表使用<el-row>流式布局实现图片和模板的分栏布局

                        <div>
          <el-row :gutter="10" style="margin: 10px 0" v-for="item in tableData" :key="item.id">
            <el-col :span="7" style="margin-bottom: 10px">
              <img :src="item.img" alt="" style="width: 100%;height:200px; ">
            </el-col>
            <el-col :span="16" style="margin: 10px 0 10px 20px">
              <div class="fontCss" style="font-weight: bold;color: #330000;font-size: 24px " @click="$router.push('/front/'+item.type)">
                {{ item.name }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                文书描述:{{ item.user }}
              </div>
              <div style="margin-top: 10px" class="fontCss">
                文书类别:{{ item.blogType }}
              </div>
            </el-col>
          </el-row>
 
          <div style="padding: 10px 0">
          </div>
        </div>

并绑定静态的type,使得前端展示小组设计可生成的文书类型如下

                tableData: [
        {"id":0,
          "name":"最好的判决书模板",
          "user":"判决书是法院对案件审理后,根据查明和认定的案件事实,依照法律规定,对案件实体问题作出的权威性判定。它标志着案件审理的终结,且判决书一经作出即具有法律效力,非经法定程序不得变更或撤销。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"判决书",
          "type":"PanJueShu",
        },
 
        {"id":1,
          "name":"最好的声明书(社会组织声明无违法记录用)模板",
          "user":"证明社会组织合法性、满足法律程序要求、促进公正审判、加强社会监督、规范社会组织行为等方面具有重要的作用",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"声明书(社会组织声明无违法记录用)",
          "type":"12shengMingShu"
 
        },
        {"id":2,
          "name":"最好的担保书(案外人提供保全或者先予执行担保用)模板",
          "user":"其目的是确保在案件审理过程中,当申请人或被申请人因各种原因无法提供担保时,有案外人能够提供必要的担保,以保障诉讼活动的顺利进行。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"担保书(案外人提供保全或者先予执行担保用)",
          "type":"2danBaoShu"
        },
        {"id":4,
          "name":"最好的民事答辩状(法人或者其他组织对民事起诉提出答辩用)模板",
          "user":"法律赋予处于被告地位的法人或其他组织的一种权利,使其能够针对原告的起诉状内容,提出自己的反驳意见和理由。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事答辩状(法人或者其他组织对民事起诉提出答辩用)",
          "type":"4mingShiDaBianZhuang"
        },
        {"id":5,
          "name":"最好的民事反诉状(法人或者其他组织提起民事反诉用)模板",
          "user":"通过提交反诉状,被告可以主张自己的权益受损,并要求法院对其进行保护。这是被告行使其诉讼权利的一种方式,",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事反诉状(法人或者其他组织提起民事反诉用)",
          "type":"5mingShiFanShuZhuang"
        },
        {"id":6,
          "name":"最好的民事再审申请书(申请再审用)模板",
          "user":"它不仅为当事人提供了一个表达不服与申诉的平台,还是启动再审程序、保障司法公正以及促进法律程序完善的重要工具。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事再审申请书(申请再审用)",
          "type":"6MingShiZaiShenTongZhiShu"
        },
        {"id":7,
          "name":"最好的民事上诉状模板",
          "user":"民事起诉状是当事人向人民法院提起民事诉讼的书面申请。它是启动民事诉讼程序的重要法律文件,用以明确诉讼请求、事实和理由,以及相关的证据和法律依据。提交民事起诉状后,法院将进行审查,如果符合受理条件,将立案并通知被告应诉。随后,法院将组织双方当事人进行证据交换、庭审等诉讼活动,最终作出判决或裁定。\n" +
              "\n" +
              "民事起诉状是维护当事人合法权益的重要手段,应当认真准备,确保内容真实、合法、有效。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事上诉状",
          "type":"7minShiShangShuZhuang"
         },
        {"id":8,
          "name":"最好的行政赔偿调解书(一审行政赔偿案件用)模板",
          "user":"高效解决纠纷,和谐解决争议,保护当事人合法权益,推动法治政府建设,提供法律依据",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"行政赔偿调解书(一审行政赔偿案件用)",
          "type":"8xingZhengPeiChangTiaoJieShu"
        },
        {"id":9,
          "name":"最好的二审应诉通知书(通知被上诉人用)模板",
          "user":"明确告知被上诉人其作为案件当事人,在二审程序中需要履行的诉讼义务。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"二审应诉通知书(通知被上诉人用)",
          "type":"9yingSuTongZhiShu"
        },
        {"id":10,
          "name":"最好的异议书(对管辖权提出异议用)模板",
          "user":"主要用于当事人对法院的管辖权提出异议,并阐述相关理由和依据。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"异议书(对管辖权提出异议用)",
          "type":"10yiYiShu"
        },
        {"id":11,
          "name":"最好的民事判决书(公示催告除权用)模板",
          "user":"民事上诉状是指当事人对第一审人民法院作出的尚未生效的民事判决或裁定不服,向上一级人民法院提起上诉的书面申请。它是民事诉讼中的一种重要法律文书,用以启动上诉程序。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"民事判决书(公示催告除权用)",
          "type":"11shangSuZhuang"
        },
        {"id":12,
          "name":"最好的不予受理支付令申请通知书(通知申请人不予受理用)模板",
          "user":"主要作用在于通知并告知债权人支付令申请的处理结果、列明不符合的条件、指引后续操作、维护法律秩序、保障当事人权益以及作为法律文件等方面。",
          "img":"http://localhost:9090/file/feb52f64c2694dc983f418fe1a22e7f9.png",
          "blogType":"不予受理支付令申请通知书(通知申请人不予受理用)",
          "type":"1buYuShouLiTongZhiShu"
        }
      ],

通过vue的点击触发路由跳转,进入设置好的各个法律文书生成的模板页面
<div class="fontCss" style="font-weight: bold;color: #330000;font-size: 24px " @click="$router.push('/front/'+item.type)">
子路由如下:

                 {
        path: 'PanJueShu',
        name: 'PanJueShu',
        component: () => import('../views/front/muban/PanJueShu.vue')
      },
      {
        path: '1buYuShouLiTongZhiShu',
        name: '1buYuShouLiTongZhiShu',
        component: () => import('../views/front/muban/1buYuShouLiTongZhiShu.vue')
      },
      {
        path: '2danBaoShu',
        name: '2danBaoShu',
        component: () => import('../views/front/muban/2danBaoShu.vue')
      },
      {
        path: '3LvShiHan',
        name: '3LvShiHan',
        component: () => import('../views/front/muban/3LvShiHan.vue')
      },
      {
        path: '4mingShiDaBianZhuang',
        name: '4mingShiDaBianZhuang',
        component: () => import('../views/front/muban/4mingShiDaBianZhuang.vue')
      },
      {
        path: '5mingShiFanShuZhuang',
        name: '5mingShiFanShuZhuang',
        component: () => import('../views/front/muban/5mingShiFanShuZhuang.vue')
      },
      {
        path: '6MingShiZaiShenTongZhiShu',
        name: '6MingShiZaiShenTongZhiShu',
        component: () => import('../views/front/muban/6MingShiZaiShenTongZhiShu.vue')
      },
      {
        path: '7minShiShangShuZhuang',
        name: '7minShiShangShuZhuang',
        component: () => import('../views/front/muban/7minShiShangShuZhuang.vue')
      },
      {
        path: '8xingZhengPeiChangTiaoJieShu',
        name: '8xingZhengPeiChangTiaoJieShu',
        component: () => import('../views/front/muban/8xingZhengPeiChangTiaoJieShu.vue')
      },
      {
        path: '9yingSuTongZhiShu',
        name: '9yingSuTongZhiShu',
        component: () => import('../views/front/muban/9yingSuTongZhiShu.vue')
      },
      {
        path: '10yiYiShu',
        name: '10yiYiShu',
        component: () => import('../views/front/muban/10yiYiShu.vue')
      },{
        path: '11shangSuZhuang',
        name: '11shangSuZhuang',
        component: () => import('../views/front/muban/11shangSuZhuang.vue')
      },
      {
        path: '12shengMingShu',
        name: '12shengMingShu',
        component: () => import('../views/front/muban/12shengMingShu.vue')
      },
      {
        path: 'hope',
        name: 'hope',
        component: () => import('../views/front/muban/hope.vue')
      },

前后端对接函数记录

登录:

                    login() {
      this.$refs['userForm'].validate((valid) => {
        if (valid) {  // 表单校验合法
          this.request.post("/user/login", this.user).then(res => {
            if(res.code === '200') {
              localStorage.setItem("user", JSON.stringify(res.data))  // 存储用户信息到浏览器
              localStorage.setItem("menus", JSON.stringify(res.data.menus))  // 存储用户信息到浏览器
 
              // 动态设置当前用户的路由
              setRoutes()
              if (res.data.role === 'ROLE_USER') {
                this.$router.push("/front/home")
                this.$message.success("登录成功")
              } else {
                this.$router.push("/")
                this.$message.success("登录成功")
              }
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      });
    },

用户获取:

                    getUser() {
      let username = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")).username : ""
      if (username) {
        // 从后台获取User数据
        this.request.get("/user/username/" + username).then(res => {
          // 重新赋值后台的最新User数据
          this.user = res.data
        })
      }
    }

文书页面:

                    load() {
      this.request.get("/blog/page", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
 
      this.request.get("/blogType").then(res => {
        this.types = res.data
      })
    },
 save() {
      this.form.content = editor.txt.html()
      this.$refs['ruleForm'].validate((valid) => {
        if (valid) {
          this.request.post("/blog", this.form).then(res => {
            if (res.code === '200') {
              this.$message.success("保存成功")
              this.dialogFormVisible = false
              this.dialogFormVisible2 = false
              this.load()
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      })
    },
    save2() {
      this.request.post("/blog", this.form).then(res => {
        if (res.code === '200') {
          this.$message.success("审核成功")
          this.dialogFormVisible2 = false
          this.load()
        } else {
          this.$message.error(res.msg)
        }
      })
    },
    handleAdd() {
      this.dialogFormVisible = true
      this.form = {}
      this.$nextTick(() => {
        if (!editor){
          editor  = new E("#richText")
          // 配置 server 接口地址
          editor.config.uploadImgServer = 'http://localhost:9090/file/uploadImg'
          editor.config.uploadFileName = 'file'
          editor.create()
        }
        editor.txt.html('')
 
        if(this.$refs.img) {
          this.$refs.img.clearFiles();
        }
        if(this.$refs.file) {
          this.$refs.file.clearFiles();
        }
      })
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true
      this.$nextTick(() => {
        if (!editor){
          editor = new E("#richText")
          // 配置 server 接口地址
          editor.config.uploadImgServer = 'http://localhost:9090/file/uploadImg'
          editor.config.uploadFileName = 'file'
          editor.create()
        }
        editor.txt.html(this.form.content)
 
        if(this.$refs.img) {
          this.$refs.img.clearFiles();
        }
        if(this.$refs.file) {
          this.$refs.file.clearFiles();
        }
      })
    },
    handleEdit2(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible2 = true
    },
    del(id) {
      this.request.delete("/blog/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.load()
        } else {
          this.$message.error("删除失败")
        }
      })
    },

文书类型:

                load() {
      this.request.get("/blogType/page", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
    },
    save() {
        this.$refs['ruleForm'].validate((valid) => {
          if (valid) {
            this.request.post("/blogType", this.form).then(res => {
              if (res.code === '200') {
                this.$message.success("保存成功")
                this.dialogFormVisible = false
                this.load()
              } else {
                this.$message.error(res.msg)
              }
            })
          }
        })
    },

轮播图和公告

                  created() {
    // this.request.get("/").then(res => {
    //   this.files = res.data
    // })
 
    this.request.get("/notice").then(res => {
      this.notices = res.data.splice(0, 10)
    })
 
    // this.request.get("/blog/recommend").then(res => {
    //   this.blogRecommends = res.data
    // })
 
    this.request.get("/file/pageHome", {
      params: {
        pageNum: this.pageNum,
        pageSize: this.pageSize,
      }
    }).then(res => {
      this.files = res.data.records
      this.total = res.data.total
 
    })
  },

用户文书展示:

                    load() {
      console.log(this.type)
      this.request.get("/blog/page/type", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
          type: this.type
        }
      }).then(res => {
        this.tableData = res.data.records
        this.total = res.data.total
      })
 
      this.request.get("/blogType").then(res => {
        this.types = res.data
      })
    },

AI回答:

                      const err = (msg) => `# *${msg}*`
      const question = this.question;
      if (!question) {
        this.answer = err("未输入内容")
        return;
      }
      AIRequest.chat(question,true).then(response =>{
        // 处理响应
        console.log(response);
        if (response.code !=0) {
          this.answer = err("发生错误,请查看服务器日志");
          return;
        }
        this.answer = response.data.answer;
      })
          .catch(error => {
            // 处理错误情况
            console.error(error);
            this.answer = err("发生错误,请查看服务器日志");
 
      })

判决书:

                 created() {
    const currentDate = new Date();
    const year = currentDate.getFullYear();
    const month = currentDate.getMonth() + 1; // getMonth() returns month from 0-11
    const day = currentDate.getDate();
    this.formattedDate = `${year}${month}${day}日`;
    this.formData.date = this.formattedDate;
 
    this.getLocation();
  },
  computed: {
    shouldHideHeader() {
      return (
          this.formData.title === this.title &&
          !this.formData.keywords &&
          !this.formData.points &&
          !this.formData.caseDetails &&
          !this.formData.result
          // !this.formData.location &&
          // this.formData.date === this.formattedDate
      );
    },
    renderAnswer() {
      return this.md.render(this.answers);
    }
  },
  methods: {
    getAnswer(){
      if (!this.checkRequired()) return;
      this.answer = true;
      this.answerList = ["你好","再见"];
      this.lawList = [["1","1","法律1"],["1","1","法律2"],["1","1","法律3"]];
       AIRequest.ask(this.formData.caseDetails).then(res =>{
         console.log(res);
         this.answerList = res.data.answerList;
         this.lawList = res.data.lawList;
       })
      /*      AiRequest.post("ai/chat",{
              input:'我偷了李四50000000元,我应该怎么逃跑'
            }).then(res =>{
                console.log(res)
              }
              )*/
    },
    checkRequired() {
      for (const item of document.querySelectorAll(`[required]`)) {
        if (!item.value) return false;
      }
      return true;
    },
    async downloadPdf() {
      if (!this.checkRequired()) return;
      const element = this.$refs.appealContent;
      const pdf = new jsPDF();
      pdf.addFont("/stzhongs.ttf", "song", "normal")
      pdf.setFont("song");
      let offset = 20;
      Array.from(element.querySelectorAll("h2,h3,p")).forEach((item) => {
        let text = item.textContent.trim();
        let split = "";
        switch (item.tagName) {
          case "H2":
            pdf.setFontSize(20);
            pdf.text(text, 105, offset, {align: "center"});
            offset += 10;
            break;
          case "H3":
            pdf.setFontSize(16);
            pdf.text(text, 10, offset);
            offset += 8;
            break;
          case "P":
            pdf.setFontSize(12);
            split = pdf.splitTextToSize(text.replace(/^\s*/mg, "  "), 190);
            pdf.text(split, 10, offset);
            offset += 6 * split.length + 2;
            break;
          default:
            break;
        }
      })
      pdf.save('appeal.pdf');
    },
    getLocation() {
      ipLocation().then(res => this.formData.location = res);
    },
    showError(error) {
      switch(error.code) {
        case error.PERMISSION_DENIED:
          this.location = "用户拒绝请求地理定位";
          break;
        case error.POSITION_UNAVAILABLE:
          this.location = "位置信息不可用";
          break;
        case error.TIMEOUT:
          this.location = "请求用户地理位置超时";
          break;
        default:
          this.location = "未知错误";
          break;
      }
    }
  }
};

锤子

978029a2a7c4386e460c3a72ff1b1d2c_9d2bf961598b4121bef49b1048425acf.webp Wait a minute!用必应搜索图片,然后筛选器->类型->透明,总能搞到好用的素材(自己写着玩不传播也不商用肯定没啥版权问题),所以没试过的一定要去试试啊啊啊(又疯一个,抬走)

众所周知,我们有一个CSS属性:cursor,来设定鼠标样式,它是可以自定义图标的,不过为了确保兼容性,要给一个default值,并确保使用的是大小合适的cur或ico文件。为保险起见我采用了32x32的ico格式(总之最好不要大于这个值)。

可以用这个网站来转换:图片转 ICO 图标 - 锤子在线工具

加入以下CSS样式,并给出一定的偏移(确保砸到的是你的点击位置):

                * {
  cursor: url("/cursor/hammer_0.ico") 12 24, default;
}
*:active {
  cursor: url("/cursor/hammer_1.ico") 12 24, default;
}   

什么?你问我哪来的两个图标?丢PS里转(四声,旋转)的(

用着锤子不顺眼了怎么办,那加一个关闭的快捷键吧。由于CSS的通配符样式不能用js控制,但我们有标签啊:

let hammerOpen = true;
function createHammer() {
  const style = document.createElement("style");
  style.textContent = `* {cursor: url("/cursor/hammer_0.ico") 12 24, default !important;} *:active {cursor: url("/cursor/hammer_1.ico") 12 24, default !important;}`;
  style.id = "hammer-cursor";
  document.body.appendChild(style);
}
function hammer() {
  document.getElementById("hammer-cursor")?.remove();
  createHammer();
  document.addEventListener("keydown", (event) => {
    if (event.ctrlKey && event.key.toUpperCase() === "F2") {
      event.preventDefault();
      hammerOpen = !hammerOpen;
      document.getElementById("hammer-cursor")?.remove();
      if (hammerOpen) createHammer();
    }
  });
}

按下Ctrl+F2开启或关闭锤子。

锤子没有打击感怎么办?加特效!

首先给出一个圆形的样式:

.click-circle {
  --top: 0px;
  --left: 0px;
  font-size: 24px;
  border-radius: 50%;
  position: fixed;
  width: 1em;
  height: 1em;
  top: calc(var(--top) - .5em);
  left: calc(var(--left) - .5em);
  background: grey;
  z-index: 9999;
  pointer-events: none;
}

添加点击事件,创建并插入一个span,运行一次动画后删除:

function ripple(event) {
  const circle = document.createElement("span");
  circle.classList.add("click-circle");
  circle.style.setProperty("--left", `${event.clientX}px`);
  circle.style.setProperty("--top", `${event.clientY}px`);
  const animation = circle.animate([
    {
      transform: "scale(0)",
      opacity: 1,
    },
    {
      transform: "scale(2)",
      opacity: 0
    }
  ], {
    duration: 500,
    easing: "ease-out"
  });
  animation.onfinish = () => circle.remove();
  document.body.append(circle);
}

导出hammer和ripple函数,在项目的App.vue中生效:

import { hammer, ripple } from "@/utils/ripple";
export default {
  created() {
    document.removeEventListener("mousedown", ripple);
    document.addEventListener("mousedown", ripple);
    hammer();
  }
}

结束。现在我们有一个锤子鼠标了!

基于vue的地理定位

在 Vue.js 中实现地理定位功能,需要利用 HTML5 的 Geolocation API

HTML5 的 Geolocation API 是一种允许网页应用程序访问用户设备位置的技术。它利用多种技术(如 GPS、IP 地址、Wi-Fi 和蜂窝基站信号等)来确定设备的地理位置。

Geolocation API 原理 Geolocation API 通过调用设备的地理定位硬件或网络服务来获取用户的位置信息。具体来说,它使用以下几种方法来确定位置:

GPS(全球定位系统):主要用于移动设备,精度高,但耗电量大。 IP 地址:根据用户的 IP 地址推测位置,精度较低,通常用于桌面设备。 Wi-Fi:通过扫描附近的 Wi-Fi 网络来确定位置,精度较高,但需要连接到 Wi-Fi 网络。 蜂窝基站信号:通过移动设备连接的蜂窝网络确定位置,精度介于 GPS 和 IP 地址之间。 Geolocation API 方法 Geolocation API 提供了三个主要方法: navigator.geolocation.getCurrentPosition(success, error, options)

获取当前设备的位置。 参数: success:成功回调函数,接收一个 Position 对象。 error:错误回调函数,接收一个 PositionError 对象。 options:一个可选对象,用于配置请求的参数。 navigator.geolocation.watchPosition(success, error, options)

监视设备位置的变化。 参数与 getCurrentPosition 相同,但返回一个 watchID,可用于取消监视。 navigator.geolocation.clearWatch(watchID)

取消位置监视。 参数: watchID:watchPosition 返回的 ID。 Position 对象 Position 对象包含以下属性:

coords:一个 Coordinates 对象,包含以下信息: latitude:纬度。 longitude:经度。 altitude:海拔高度(可能为 null)。 accuracy:位置精度,以米为单位。 altitudeAccuracy:海拔高度的精度(可能为 null)。 heading:行进方向,以度为单位(可能为 null)。 speed:速度,以米/秒为单位(可能为 null)。 timestamp:时间戳,表示获取位置信息的时间。 PositionError 对象 PositionError 对象包含以下属性:

code:错误代码,表示错误类型。 1(PERMISSION_DENIED):用户拒绝了地理定位请求。 2(POSITION_UNAVAILABLE):无法获取位置信息。 3(TIMEOUT):获取位置信息的请求超时。 message:详细的错误信息。 以下代码是完整的流程:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Geolocation API Example</title>
</head>
<body>
  <h1>Geolocation Example</h1>
  <button οnclick="getLocation()">Get Location</button>
  <p id="location"></p>

  <script>
    function getLocation() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showPosition, showError);
      } else {
        document.getElementById("location").innerHTML = "Geolocation is not supported by this browser.";
      }
    }

    function showPosition(position) {
      document.getElementById("location").innerHTML = 
        "Latitude: " + position.coords.latitude + 
        "<br>Longitude: " + position.coords.longitude;
    }

    function showError(error) {
      switch(error.code) {
        case error.PERMISSION_DENIED:
          document.getElementById("location").innerHTML = "User denied the request for Geolocation.";
          break;
        case error.POSITION_UNAVAILABLE:
          document.getElementById("location").innerHTML = "Location information is unavailable.";
          break;
        case error.TIMEOUT:
          document.getElementById("location").innerHTML = "The request to get user location timed out.";
          break;
        case error.UNKNOWN_ERROR:
          document.getElementById("location").innerHTML = "An unknown error occurred.";
          break;
      }
    }
  </script>
</body>
</html>

权限问题:使用 Geolocation API 需要用户的明确许可,因此在调用 getCurrentPosition 或 watchPosition 时,浏览器会提示用户进行授权。)

系统中的实例:

                getLocation() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(this.showPosition, this.showError);
  } else {
    this.location = "无法获取定位信息";
  }
},
async showPosition(position) {
  const lat = position.coords.latitude;
  const lon = position.coords.longitude;
  this.center = [lat, lon];
  this.marker = [lat, lon]
  const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=zh`);
  const data = await response.json();
  this.formData.location = `${data.principalSubdivision}${data.city}`;
},
showError(error) {
  switch(error.code) {
    case error.PERMISSION_DENIED:
      this.location = "用户拒绝请求地理定位";
      break;
    case error.POSITION_UNAVAILABLE:
      this.location = "位置信息不可用";
      break;
    case error.TIMEOUT:
      this.location = "请求用户地理位置超时";
      break;
    case error.UNKNOWN_ERROR:
      this.location = "未知错误";
      break;
  }
}