Selaa lähdekoodia

feat(system): 添加服务协议功能并优化使用条款管理

- 新增服务协议查询接口和服务协议最大版本查询接口
- 添加服务协议预览页面和路由
- 优化使用条款管理页面,增加类型筛选和内容格式化功能
- 更新权限白名单,添加服务协议相关路由
fugui001 3 kuukautta sitten
vanhempi
commit
757a4c1202

+ 3 - 0
src/api/system/business/ofService/types.ts

@@ -52,6 +52,7 @@ export interface OfServiceForm extends BaseEntity {
    */
   isDefault?: number;
 
+  type?: string;
 }
 
 export interface OfServiceQuery extends PageQuery {
@@ -80,4 +81,6 @@ export interface OfServiceQuery extends PageQuery {
    * 日期范围参数
    */
   params?: any;
+
+  type?: string;
 }

+ 10 - 0
src/api/system/business/terms/index.ts

@@ -72,3 +72,13 @@ export const getSportTermsListMax = (): AxiosPromise<TermsVO> => {
     method: 'get'
   });
 };
+/**
+ * 查询默认数据展示H5页面 接口
+ * @param id
+ */
+export const getServiceAgreementMax = (): AxiosPromise<TermsVO> => {
+  return request({
+    url: '/business/terms/getServiceAgreementMax',
+    method: 'get'
+  });
+};

+ 2 - 1
src/permission.ts

@@ -21,7 +21,8 @@ const whiteList = [
   '/ofService',
   '/policy/*',
   '/policy',
-  '/terms'
+  '/terms',
+  '/agreement'
 ];
 
 const isWhiteList = (path: string) => {

+ 5 - 0
src/router/index.ts

@@ -103,6 +103,11 @@ export const constantRoutes: RouteRecordRaw[] = [
     path: '/terms',
     component: () => import('@/views/system/business/terms/indexSportPreview.vue'),
     hidden: true
+  },
+  {
+    path: '/agreement',
+    component: () => import('@/views/system/business/agreement/indexAgreePreview.vue'),
+    hidden: true
   }
 ];
 

+ 201 - 0
src/views/system/business/agreement/indexAgreePreview.vue

@@ -0,0 +1,201 @@
+<template>
+  <div class="tos-container">
+    <el-card>
+      <div class="tos-content" v-html="tosContent"></div>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import DOMPurify from 'dompurify';
+import { getServiceAgreementMax } from '@/api/system/business/terms';
+
+const tosContent = ref('');
+const language = ref('zh-CN');
+
+onMounted(() => {
+  fetchTos();
+});
+
+const fetchTos = async () => {
+  try {
+    const res = await getServiceAgreementMax();
+    // 处理获取到的内容,确保格式良好
+    const formattedContent = handleContentFormatting(res.data.content || '<p>内容加载失败</p>');
+    tosContent.value = DOMPurify.sanitize(formattedContent);
+  } catch (error) {
+    console.error('内容加载失败', error);
+    tosContent.value = '<p>内容加载失败</p>';
+  }
+};
+
+// 完善内容格式化处理
+const handleContentFormatting = (content: string): string => {
+  if (!content) return '<p>暂无内容</p>';
+
+  // 移除多余的空白字符,但保留必要的换行
+  let formattedContent = content
+    .replace(/\r\n/g, '\n')
+    .replace(/\r/g, '\n')
+    .replace(/\n\s*\n/g, '\n\n') // 合并多个空行为两个换行
+    .trim();
+
+  // 如果内容已经是HTML格式(包含标签),则不进行额外处理
+  if (/<[^>]+>/.test(formattedContent)) {
+    return formattedContent;
+  }
+
+  // 如果是纯文本,转换为基本的HTML格式
+  // 将连续的换行转换为段落
+  formattedContent = formattedContent
+    .split('\n\n')
+    .map((paragraph) => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
+    .join('');
+
+  return formattedContent;
+};
+</script>
+
+<style scoped>
+.tos-container {
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.tos-content {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.6;
+  color: #333;
+  word-break: break-word;
+}
+
+/* 标题样式 */
+.tos-content h1 {
+  font-size: 24px;
+  font-weight: bold;
+  margin: 24px 0 16px 0;
+  color: #2c3e50;
+  text-align: center;
+}
+
+.tos-content h2 {
+  font-size: 20px;
+  font-weight: bold;
+  margin: 20px 0 12px 0;
+  color: #34495e;
+  border-bottom: 1px solid #eee;
+  padding-bottom: 8px;
+}
+
+.tos-content h3 {
+  font-size: 18px;
+  font-weight: bold;
+  margin: 16px 0 10px 0;
+  color: #555;
+}
+
+/* 段落样式 */
+.tos-content p {
+  margin: 12px 0;
+  line-height: 1.7;
+}
+
+/* 列表样式 */
+.tos-content ul,
+.tos-content ol {
+  margin: 12px 0;
+  padding-left: 24px;
+}
+
+.tos-content li {
+  margin: 8px 0;
+  line-height: 1.6;
+}
+
+.tos-content ul li {
+  list-style-type: disc;
+}
+
+.tos-content ol li {
+  list-style-type: decimal;
+}
+
+/* 强调文本 */
+.tos-content strong {
+  font-weight: bold;
+  color: #2c3e50;
+}
+
+.tos-content em {
+  font-style: italic;
+}
+
+/* 链接样式 */
+.tos-content a {
+  color: #3498db;
+  text-decoration: none;
+}
+
+.tos-content a:hover {
+  text-decoration: underline;
+}
+
+/* 代码样式 */
+.tos-content code {
+  background-color: #f8f9fa;
+  padding: 2px 4px;
+  border-radius: 3px;
+  font-family: 'Courier New', monospace;
+  font-size: 13px;
+}
+
+/* 引用样式 */
+.tos-content blockquote {
+  border-left: 4px solid #ddd;
+  margin: 16px 0;
+  padding: 8px 16px;
+  background-color: #f9f9f9;
+  color: #666;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .tos-container {
+    padding: 15px;
+  }
+
+  .tos-content {
+    font-size: 13px;
+  }
+
+  .tos-content h1 {
+    font-size: 20px;
+    margin: 20px 0 14px 0;
+  }
+
+  .tos-content h2 {
+    font-size: 18px;
+    margin: 18px 0 10px 0;
+  }
+
+  .tos-content h3 {
+    font-size: 16px;
+    margin: 14px 0 8px 0;
+  }
+
+  .el-card {
+    width: 100%;
+    margin: 0 auto;
+  }
+}
+
+/* 处理可能的空白内容 */
+.tos-content:empty::before {
+  content: '暂无内容';
+  color: #999;
+  font-style: italic;
+}
+</style>

+ 27 - 10
src/views/system/business/ofService/index.vue

@@ -4,9 +4,15 @@
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="条款标题" prop="title">
+            <el-form-item label="标题" prop="title">
               <el-input v-model="queryParams.title" placeholder="请输入条款标题" clearable @keyup.enter="handleQuery" />
             </el-form-item>
+            <el-form-item label="类型" prop="gameType">
+              <el-select v-model="queryParams.type" placeholder="请选择" clearable>
+                <el-option label="所有" :value="''" />
+                <el-option v-for="dict in service_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -42,9 +48,13 @@
       <el-table v-loading="loading" border :data="ofServiceList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="主键" align="center" prop="id" v-if="false" />
-        <el-table-column label="条款标题" align="center" prop="title" />
-        <el-table-column label="条款内容" align="center" prop="contentText" :show-overflow-tooltip="true" />
-        <el-table-column label="语言代码" align="center" prop="language" />
+        <el-table-column label="标题" align="center" prop="title" />
+        <el-table-column label="内容" align="center" prop="contentText" :show-overflow-tooltip="true" />
+        <el-table-column label="条款类型" align="center">
+          <template #default="scope">
+            {{ { 1: 'APP使用条款', 2: '关于三湘杯', 3: '运动员声明', 4: '服务协议', 5: '隐私协议' }[scope.row.type] }}
+          </template>
+        </el-table-column>
         <el-table-column label="是否为默认版本" align="center" prop="isDefault">
           <template #default="scope">
             <el-tag v-if="scope.row.isDefault === 1" type="success">是</el-tag>
@@ -69,15 +79,20 @@
     <!-- 添加或修改使用条款管理对话框 -->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
       <el-form ref="ofServiceFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="条款标题" prop="title">
-          <el-input v-model="form.title" placeholder="请输入条款标题" />
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="form.title" placeholder="请输入标题" />
         </el-form-item>
-        <el-form-item label="条款内容" prop="content">
+        <el-form-item label="内容" prop="content">
           <editor v-model="form.content" :min-height="192" />
         </el-form-item>
 <!--        <el-form-item label="语言代码,如 zh-CN, en-US" prop="language">
           <el-input v-model="form.language" placeholder="请输入语言代码,如 zh-CN, en-US" />
         </el-form-item>-->
+        <el-form-item label="条款类型" prop="type">
+          <el-select aria-required="true" v-model="form.type" placeholder="请选择">
+            <el-option v-for="dict in service_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+          </el-select>
+        </el-form-item>
         <el-form-item label="默认版本" prop="isDefault">
           <el-radio-group v-model="form.isDefault">
             <el-radio :label="1">是</el-radio>
@@ -100,6 +115,7 @@ import { listOfService, getOfService, delOfService, addOfService, updateOfServic
 import { OfServiceVO, OfServiceQuery, OfServiceForm } from '@/api/system/business/ofService/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { service_type } = toRefs<any>(proxy?.useDict('service_type'));
 
 const ofServiceList = ref<OfServiceVO[]>([]);
 const buttonLoading = ref(false);
@@ -138,8 +154,9 @@ const data = reactive<PageData<OfServiceForm, OfServiceQuery>>({
   },
   rules: {
     id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
-    title: [{ required: true, message: '条款标题不能为空', trigger: 'blur' }],
-    content: [{ required: true, message: '条款内容不能为空', trigger: 'blur' }]
+    title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
+    content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
+    type: [{ required: true, message: '条款不能为空', trigger: 'blur' }]
   }
 });
 
@@ -222,7 +239,7 @@ const submitForm = () => {
 /** 删除按钮操作 */
 const handleDelete = async (row?: OfServiceVO) => {
   const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除使用条款管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await proxy?.$modal.confirm('是否确认删除编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
   await delOfService(_ids);
   proxy?.$modal.msgSuccess('删除成功');
   await getList();