zhoujump 2 miesięcy temu
rodzic
commit
7db4e6c3c3

+ 18 - 13
src/App.vue

@@ -23,7 +23,7 @@ export default {
       menuRouter: [],
       menuActive: 0,
       breadcrumb: [],
-      isCollapse: false,
+      isCollapse: false
     }
   },
   created() {
@@ -46,26 +46,31 @@ export default {
         this.refreshRoute()
       })
       this.$router.beforeEach((to, from, next) => {
-        if (!router.app.$store.getters.token) {
-          if (to.path === '/login') {
+        if (router.app.$store.getters.token) {
+          if (router.app.$store.getters.user.isAdmin) {
+            this.isAnimation = true
+            setTimeout(() => {
+              this.isAnimation = false
+              next()
+            }, 400)
+          } else {
+            next({
+              name: 'userForm'
+            })
+          }
+        } else {
+          if (to.name === 'login' || to.name === 'userRegister' || to.name === 'userForm') {
             next()
           } else {
-            next('/login')
+            next({
+              name: 'login'
+            })
           }
         }
-        this.isAnimation = true
-        setTimeout(() => {
-          this.isAnimation = false
-          next()
-        }, 400)
       })
-      if (!router.app.$store.getters.token) {
-        this.$router.push('/login')
-      }
     },
     refreshRoute() {
       this.menuRouter = this.$router.options.routes[0].children
-      console.log(this.menuRouter)
       this.breadcrumb = this.$route.matched
       this.menuRouter.forEach((item, index) => {
         if (item.name === this.$route.name) {

+ 31 - 4
src/api/form.js

@@ -1,5 +1,4 @@
 import request from '@/utils/request'
-
 /**
  * 获取表单列表
  * @param page
@@ -18,7 +17,6 @@ export function getFormList(page, page_size, template_name){
     }
   })
 }
-
 /**
  * 获取表单详情
  * @param template_id
@@ -33,7 +31,6 @@ export function getFormInfo(template_id){
     }
   })
 }
-
 /**
  * 保存展会模板
  * @param id
@@ -54,7 +51,6 @@ export function saveForm(id,template_name,description,fields){
     }
   })
 }
-
  /**
  * 删除表单
  * @param id
@@ -87,3 +83,34 @@ export function saveForm(id,template_name,description,fields){
      }
    })
  }
+/**
+ * 发送邀请函
+ * @param form_ids
+ * @returns {*}
+ */
+ export function sentInvitation(form_ids) {
+   return request({
+     url: '/api/form/send-invitation',
+     method: 'post',
+     data:{
+       form_ids
+     }
+   })
+ }
+/**
+ * 提交表单接口
+ * @param expo_id
+ * @param form_data
+ * @returns {AxiosPromise}
+ */
+export function submitForm(expo_id,form_data) {
+   return request({
+     url: '/api/form/info',
+     method: 'post',
+     data:{
+       expo_id,
+       form_data
+     }
+   })
+}
+

+ 36 - 0
src/api/system.js

@@ -37,3 +37,39 @@ export function upload(file,title,alt,url,filename,size,ext,pic,resolution,mine_
     }
   })
 }
+
+/**
+ * 获取邮件设置
+ * @returns {*}
+ */
+export function getMailSettings() {
+  return request({
+    url: '/api/user/mail-setting',
+    method: 'get'
+  })
+}
+
+/**
+ * 设置邮件设置
+ * @param id
+ * @param from_name
+ * @param from_email
+ * @param smtp_port
+ * @param smtp_server
+ * @param auth_code
+ * @returns {*}
+ */
+export function saveMailSetting(id,from_name,from_email,smtp_port,smtp_server,auth_code) {
+  return request({
+    url: '/api/user/mail-setting',
+    method: 'post',
+    data: {
+      id,
+      from_name,
+      from_email,
+      smtp_port,
+      smtp_server,
+      auth_code
+    }
+  })
+}

+ 59 - 2
src/api/user.js

@@ -6,9 +6,11 @@ import request from '@/utils/request'
  * @param {string} password 密码
  * @param {number} login_type 登录类型 0:帐号密码登录 1:短信登录 2:微信登录 3:QQ登录
  * @param {number} login_portal 登录入口 0:web 1:app
+ * @param phone
+ * @param valid_code
  * @returns {*}
  */
-export function login(user_name, password, login_type, login_portal) {
+export function login(user_name, password, login_type, login_portal,phone,valid_code) {
   return request({
     url: '/api/user/login',
     method: 'post',
@@ -16,7 +18,9 @@ export function login(user_name, password, login_type, login_portal) {
       user_name,
       password,
       login_type,
-      login_portal
+      login_portal,
+      phone,
+      valid_code
     }
   })
 }
@@ -42,3 +46,56 @@ export function logout() {
     params: {}
   })
 }
+/**
+ * 发送短信接口
+ * @param phone
+ * @param country_code
+ * @returns {*}
+ */
+export function sendSmsCode(phone,country_code) {
+  return request({
+    url: '/api/user/get-phone-code',
+    method: 'post',
+    params: {
+      phone,
+      country_code
+    }
+  })
+}
+/**
+ * 发送邮件验证阿门接口
+ * @param email
+ * @returns {*}
+ */
+export function sentEmailCode(email) {
+  return request({
+    url: '/api/user/get-reg-email-code',
+    method: 'post',
+    params: {
+      email
+    }
+  })
+}
+
+/**
+ * 注册接口
+ * @param email
+ * @param phone
+ * @param valid_code
+ * @param register_type
+ * @param country_code
+ * @returns {*}
+ */
+export function  register(email,phone,valid_code,register_type,country_code) {
+  return request({
+    url: '/api/user/register',
+    method: 'post',
+    data: {
+      email,
+      phone,
+      valid_code,
+      register_type,
+      country_code
+    }
+  })
+}

+ 1 - 1
src/layout/index.vue

@@ -5,7 +5,7 @@
         <img src="" alt="">
       </div>
       <div class="layout-menu">
-        <el-menu unique-opened="true" class="layout-menu-inner" :collapse="isCollapse" :default-active="menuActive+''">
+        <el-menu :unique-opened="true" class="layout-menu-inner" :collapse="isCollapse" :default-active="menuActive+''">
           <template v-for="(route,index) in menuRouter">
             <template v-if="!route.meta.hidden">
               <el-submenu :index="index+''" v-if="route.children">

+ 18 - 12
src/router/index.js

@@ -7,7 +7,7 @@ export const constantRoutes = [
   {
     path: '/',
     component: Layout,
-    redirect: '/dashboard',
+    redirect: '/user/register',
     meta: { title: '首页' },
     children: [
       {
@@ -82,17 +82,17 @@ export const constantRoutes = [
         path: 'exhibitor',
         component: () => import('@/views/exhibitorManage/index'),
         name: 'ExhibitorManage',
-        meta: { title: '展管理', icon: 'el-icon-office-building', roles: 'exhibitor' },
+        meta: { title: '展管理', icon: 'el-icon-office-building', roles: 'exhibitor' },
         redirect: '/exhibitor/list',
         children: [
           {
             component: () => import('@/views/exhibitorManage/exhibitorList'),
             path: 'list',
             name: 'exhibitorManageList',
-            meta: { title: '展管理', icon: 'el-icon-edit', roles: 'exhibitor', func: [
+            meta: { title: '展管理', icon: 'el-icon-edit', roles: 'exhibitor', func: [
               {
                 roles: 'exhibitor.add',
-                name: '添加展'
+                name: '添加展'
               },
               {
                 roles: 'exhibitor.import',
@@ -108,13 +108,13 @@ export const constantRoutes = [
             component: () => import('@/views/exhibitorManage/exhibitorSetting'),
             name: 'exhibitorEdit',
             path: 'edit/:id',
-            meta: { title: '配置展', icon: 'el-icon-setting', roles: 'exhibitor.setting', hidden: true }
+            meta: { title: '配置展', icon: 'el-icon-setting', roles: 'exhibitor.setting', hidden: true }
           },
           {
             component: () => import('@/views/exhibitorManage/exhibitorSetting'),
             name: 'exhibitorAdd',
             path: 'add',
-            meta: { title: '添加展', icon: 'el-icon-document-add', roles: 'exhibitor.add' }
+            meta: { title: '添加展', icon: 'el-icon-document-add', roles: 'exhibitor.add' }
           }
         ]
       },
@@ -152,12 +152,6 @@ export const constantRoutes = [
         redirect: '/setting/exhibitor',
         children: [
           {
-            path: 'exhibitor',
-            component: () => import('@/views/setting/exhibitorSetting'),
-            name: 'exhibitorSetting',
-            meta: { title: '主办方信息配置', icon: 'el-icon-data-line', roles: 'setting.exhibitor' }
-          },
-          {
             path: 'account',
             component: () => import('@/views/setting/accountSetting'),
             name: 'accountSetting',
@@ -211,6 +205,18 @@ export const constantRoutes = [
     component: () => import('@/views/login/index'),
     hidden: true
   },
+  {
+    path: '/user/form',
+    name: 'userForm',
+    component: () => import('@/views/user/form.vue'),
+    hidden: true
+  },
+  {
+    path: '/user/register',
+    name: 'userRegister',
+    component: () => import('@/views/user/register.vue'),
+    hidden: true
+  },
   { path: '*', redirect: '/404', hidden: true }
 ]
 const createRouter = () => new Router({

+ 14 - 4
src/store/modules/user.js

@@ -29,16 +29,22 @@ export default {
      */
     login({ commit }, payload) {
       return new Promise((resolve, reject) => {
-        login(payload.username, payload.password, 0, 0).then(response => {
+        login(payload.username, payload.password, payload.login_type, payload.login_portal,payload.phone,payload.vaild_code).then(response => {
           commit('SET_TOKEN', response.data.api_token)
           getInfo().then(response => {
-            console.log(response)
+            let isAdmin = false
+            response.data.app_list.forEach(app => {
+              if (app.app_code === 'EXPOREG') {
+                isAdmin = true
+              }
+            })
             commit('SET_USER', {
               username: response.data.user_name,
               nickname: response.data.nick_name,
               avatar: response.data.avatar,
               email: response.data.email,
-              phone: response.data.phone
+              phone: response.data.phone,
+              isAdmin: isAdmin
             })
             if (payload.savePassword) {
               const savedAccount = {
@@ -49,7 +55,11 @@ export default {
             } else {
               localStorage.removeItem('savedAccount')
             }
-            resolve('登录成功')
+            if (!isAdmin) {
+              reject('您没有权限访问!')
+            } else {
+              resolve('登录成功')
+            }
           })
         }).catch(error => {
           console.log(error)

+ 5 - 5
src/utils/request.js

@@ -70,8 +70,8 @@ export const createRequest = function(baseURL) {
             type: 'error',
             duration: 5 * 1000
           })
-          store.dispatch('user/reLogout').then(() => {
-            location.reload()
+          store.dispatch('logout').then(() => {
+
           })
         } else {
           Message({
@@ -88,8 +88,8 @@ export const createRequest = function(baseURL) {
             cancelButtonText: 'Cancel',
             type: 'warning'
           }).then(() => {
-            store.dispatch('user/reLogout').then(() => {
-              location.reload()
+            store.dispatch('logout').then(() => {
+
             })
           })
         }
@@ -106,7 +106,7 @@ export const createRequest = function(baseURL) {
       })
       console.log(error.config.url)
       if (error.config.url === '/api/admin/user/get-admin-user') {
-        store.dispatch('user/reLogout').then(() => {
+        store.dispatch('logout').then(() => {
           location.reload()
         })
       }

+ 0 - 687
src/utils/worker.js

@@ -1,687 +0,0 @@
-/**
- * @fileoverview 一个 SharedWorker,用于处理 Web 应用程序的后台任务,例如 AI 内容生成、翻译和 SEO 优化。
- */
-
-'use strict'
-
-// ===================================================================================
-// 1. 状态与配置
-// ===================================================================================
-
-/**
- * Worker 的全局状态。
- */
-const state = {
-  token: null,
-  mainPort: null, // 主“守护”页面的端口
-  helperPort: null, // AI 助手 UI 的端口
-  aiUrl: '',
-  baseURL: '',
-  settings: {
-    maxRetry: 5, // 默认全局重试次数限制
-    retryOne: 3, // 默认单个任务重试次数限制
-    debug: false
-  },
-  seoConfig: {},
-  workList: [],
-  isWorkLoopActive: false,
-  globalRetryCount: 0
-}
-
-/**
- * API 端点定义。
- * 使用 getter 确保 state 中的 baseURL 始终是最新的。
- */
-const api = {
-  _constructUrl(path) { return state.baseURL + path },
-  get getBlog() { return this._constructUrl('api/blog/info') },
-  get getBlogTags() { return this._constructUrl('api/blog/get-tag-list') },
-  get getBlogTypes() { return this._constructUrl('api/blog/get-type-list') },
-  get getBlogPlates() { return this._constructUrl('api/blog/get-plate-list') },
-  get getBlogList() { return this._constructUrl('/api/blog/get-list') },
-  get saveBlog() { return this._constructUrl('api/blog/save') },
-  get getProduct() { return this._constructUrl('api/product/info') },
-  get getProductTags() { return this._constructUrl('api/product/get-tag-list') },
-  get getProductTypes() { return this._constructUrl('api/product/get-type-list') },
-  get saveProduct() { return this._constructUrl('api/product/save') },
-  get getMeeting() { return this._constructUrl('api/meeting/info') },
-  get getMeetingTypes() { return this._constructUrl('api/meeting/get-type-list') },
-  get getMeetingTags() { return this._constructUrl('api/meeting/get-tag-list') },
-  get saveMeeting() { return this._constructUrl('api/meeting/save') },
-  get getStaticPage() { return this._constructUrl('api/static-page/get-info') },
-  get saveStaticPage() { return this._constructUrl('api/static-page/save') },
-  get transStatus() { return this._constructUrl('api/web/page/update-trans-status') }
-}
-
-// ===================================================================================
-// 2. WORKER 核心逻辑
-// ===================================================================================
-
-console.log('Worker: 脚本已加载,准备接收连接。')
-
-/**
- * 处理到 SharedWorker 的新连接。
- */
-onconnect = (event) => {
-  const port = event.ports[0]
-  console.log('Worker: 新连接已建立。')
-
-  port.onmessage = (e) => {
-    const { type, data, message } = e.data
-    console.log(`Worker: 接收到类型为 "${type}" 的消息`, e.data)
-
-    // 仅启动一次工作循环。
-    if (!state.isWorkLoopActive) {
-      state.isWorkLoopActive = true
-      startWorkLoop()
-    }
-
-    switch (type) {
-      case 'init':
-        handleInit(port, e.data)
-        break
-      case 'helper':
-        state.helperPort = port
-        console.log('Worker: AI 助手页面已连接。')
-        break
-      case 'connect':
-        console.log('Worker: 普通页面已连接。')
-        if (state.mainPort) {
-          state.mainPort.postMessage(createMessage('connect', message, {}))
-        }
-        break
-      case 'translate':
-      case 'seo':
-      case 'write':
-        handleNewTask(type, data, port, message)
-        break
-      default:
-        console.warn(`Worker: 接收到未知消息类型 "${type}"`)
-    }
-  }
-
-  port.postMessage(createMessage('message', '新页面已连接'))
-}
-
-/**
- * 使用主页面提供的必要数据初始化 Worker。
- * @param {MessagePort} port - 连接页面的端口。
- * @param {object} eventData - 消息事件中的数据对象。
- */
-function handleInit(port, eventData) {
-  state.mainPort = port
-  const { token, aiUrl, setting, seoConfig, baseURL } = eventData.data
-  state.token = token
-  state.aiUrl = aiUrl
-  state.settings = { ...state.settings, ...setting }
-  state.seoConfig = seoConfig
-  state.baseURL = baseURL
-  console.log('Worker: 主页面已连接,状态已初始化。', { settings: state.settings, baseURL: state.baseURL })
-  port.postMessage(createMessage('init', eventData.message, {}))
-}
-
-/**
- * 处理新任务的提交。
- * @param {string} type - 任务类型 ('translate', 'seo', 'write')。
- * @param {object} data - 任务所需的数据。
- * @param {MessagePort} port - 提交任务的端口。
- * @param {string} message - 附带的消息。
- */
-function handleNewTask(type, data, port, message) {
-  if (addTask(type, data, port)) {
-    const successMsg = createMessage('success', message)
-    // 通知所有相关端口任务已成功入队
-    broadcastMessage(createMessage(type, `新的 ${type} 任务已添加到队列。`))
-    port.postMessage(successMsg)
-  } else {
-    port.postMessage(createMessage('error', '创建任务失败,任务可能已存在。'))
-  }
-}
-
-/**
- * 处理 workList 中任务的主循环。
- */
-async function startWorkLoop() {
-  console.log('Worker: 工作循环已启动。')
-  while (true) {
-    const workItem = state.workList.find(w => w.state === 'waiting')
-
-    if (!workItem || !state.token) {
-      await new Promise(resolve => setTimeout(resolve, 1000))
-      continue
-    }
-
-    workItem.state = 'running'
-    console.log(`Worker: 开始执行任务 ${workItem.id},类型为 ${workItem.type}`)
-    broadcastMessage(createMessage('update', `开始任务: ${workItem.type} (ID: ${workItem.data.id || ''})`))
-
-    try {
-      let taskResult
-      switch (workItem.type) {
-        case 'translate':
-          taskResult = await doTranslateTask(workItem)
-          break
-        case 'seo':
-          taskResult = await doSeoTask(workItem)
-          break
-        case 'write':
-          taskResult = await doWriteTask(workItem)
-          break
-        default:
-          throw new Error(`未知的任务类型: ${workItem.type}`)
-      }
-
-      // 关键改动:先更新列表,再发送消息
-      workSuccess(workItem)
-
-      // 使用任务返回的结果来广播消息
-      broadcastMessage(createMessage('success', taskResult.message, {}, taskResult.response))
-    } catch (error) {
-      console.error(`Worker: 任务 ${workItem.id} 执行失败。`, error)
-      broadcastMessage(createMessage('error', `类型为 ${workItem.type} 的任务失败。详情请查看控制台。`, {}, error.message))
-      workFail(workItem)
-    }
-  }
-}
-
-// ===================================================================================
-// 3. 任务管理
-// ===================================================================================
-
-/**
- * 如果不存在相似任务,则向工作队列中添加一个新任务。
- * @param {string} type - 任务类型 ('translate', 'seo', 'write')。
- * @param {object} data - 任务特定数据,必须包含 'id' 和 'type' 以进行唯一性检查。
- * @param {MessagePort} port - 发起任务的端口。
- * @returns {boolean} - 如果任务被添加则返回 true,否则返回 false。
- */
-function addTask(type, data, port) {
-  const exists = state.workList.some(item =>
-    item.type === type &&
-    item.data.id === data.id &&
-    item.data.type === data.type // 内部类型,例如 'blog'
-  )
-
-  if (exists) {
-    console.warn(`类型为 "${type}" 且针对项目 ${data.id} 的任务已在队列中。`)
-    return false
-  }
-
-  const workItem = {
-    id: `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
-    type: type,
-    data: data,
-    port: port,
-    state: 'waiting',
-    retry: 0
-  }
-
-  state.workList.push(workItem)
-  console.log(`任务 ${workItem.id} 已添加。队列长度: ${state.workList.length}`)
-  return true
-}
-
-/**
- * 从列表中移除已完成的任务,并重置全局重试计数器。
- * @param {object} workItem - 成功完成的工作项。
- */
-function workSuccess(workItem) {
-  state.workList = state.workList.filter(w => w.id !== workItem.id)
-  state.globalRetryCount = 0 // 任何任务成功后重置
-  console.log(`Worker: 任务 ${workItem.id} 已成功完成。`)
-}
-
-/**
- * 处理失败的任务,并实现重试逻辑。
- * @param {object} workItem - 失败的工作项。
- */
-function workFail(workItem) {
-  // 从列表中移除,以便后续可能重新添加
-  state.workList = state.workList.filter(w => w.id !== workItem.id)
-  state.globalRetryCount++
-
-  if (state.globalRetryCount >= state.settings.maxRetry) {
-    broadcastMessage(createMessage('error', '已达到全局最大重试次数。所有任务已中止。'))
-    state.workList = [] // 清空所有任务
-    return
-  }
-
-  workItem.retry++
-  if (workItem.retry < state.settings.retryOne) {
-    workItem.state = 'waiting'
-    state.workList.push(workItem) // 添加回队列末尾
-    const msg = `任务失败,正在重新排队。尝试次数 ${workItem.retry}/${state.settings.retryOne}。`
-    console.warn(msg)
-    broadcastMessage(createMessage('warning', msg))
-  } else {
-    const msg = `任务已达到其重试上限,已被丢弃。`
-    console.error(msg, workItem.data)
-    broadcastMessage(createMessage('error', msg, {}, workItem.data))
-  }
-}
-
-// ===================================================================================
-// 4. 异步任务处理器
-// ===================================================================================
-
-/**
- * 执行一个 'write' 写作任务。
- * @param {object} work - 写作任务的工作项。
- */
-async function doWriteTask(work) {
-  const { writeInfo, typeList, tagList, template } = work.data
-  const titleList = []
-
-  // 获取现有博客标题以避免重复
-  const blogListResponse = await apiClient(api.getBlogList, 'GET', { page: 1, page_size: 999 })
-  blogListResponse.data.data.forEach(item => titleList.push(item.title))
-
-  // 构建 AI 提示
-  let templateCreatWay = ''
-  template.templateList.forEach((item, index) => {
-    let prompt = ''
-    if (item.type === 'title') {
-      prompt = `
-      第${index}部分是一个标题,其内容${item.isAi ? '需要你根据以下提示生成:' + item.value : "值为'" + item.value + "'"}。
-    `
-    } else {
-      prompt = `
-      第${index}部分是一个段落,其内容${item.isAi ? '需要你根据以下提示生成:' + item.value : "值为'" + item.value + "'"}。${item.canTitle ? '你可以在这个部分创建多个段落,以及次级标题,也就是创建的标题等级不能高于这个段落所属的那个标题' : '你只能生成一个段落,也就是只能创建一个p标签'}。
-    `
-    }
-    templateCreatWay += prompt
-  })
-
-  const requestJson = {
-    contents: [{
-      role: 'model',
-      parts: [{
-        text: `
-          无论用户使用何种语言提问,返回给用户的数据一律使用${state.settings.language}回答,除非我直接告诉你这条数据值为多少。
-          你需要按照以下规则生成指定条数的文章数据输出至blog_list数组,接下来我将告诉你单条文章数据内的每个属性该如何生成。
-          title:这是文章标题数据,内容${writeInfo.title.is_ai ? '根据文章具体内容生成' : "值为'" + writeInfo.title.text + "'"}。生成的标题不能与${JSON.stringify(titleList)}中的文章标题重复。
-          author:这是文章作者,值为${writeInfo.author}。
-          content:这是文章正文,${template.templatePrompt},使用html格式输出,你只可以使用'p','h1-h6','ul','ol','b','em','sub','sup','u','table','blockquote' 进行文章生成,${templateCreatWay}。
-          description:这是文章梗概,根据内容生成,50个字以内。
-          type_ids:这是文章类型,${writeInfo.types.is_ai ? '根据文章内容从' + JSON.stringify(typeList) + '挑选一个,将其id填入' : "值为'" + JSON.stringify(writeInfo.types.ids) + "'。这是所有可用类型数据,你可用根据id找到对应的数据,作为生成文章的参考:" + JSON.stringify(typeList) + '。'}
-          tag_ids:这是文章标签,${writeInfo.tags.is_ai ? '根据文章内容从' + JSON.stringify(tagList) + '挑选任意数量,将其id填入' : "值为'" + JSON.stringify(writeInfo.tags.ids) + "'。这是所有可用标签数据,你可用根据id找到对应的数据,作为生成文章的参考:" + JSON.stringify(tagList) + '。'}
-        `
-      }]
-    }, {
-      role: 'user',
-      parts: [{
-        text: `生成${writeInfo.number}条文章数据`
-      }]
-    }],
-    generationConfig: {
-      responseMimeType: 'application/json',
-      responseSchema: {
-        type: 'object',
-        properties: {
-          blog_list: {
-            type: 'array',
-            items: {
-              type: 'object',
-              properties: {
-                title: { type: 'string' },
-                author: { type: 'string' },
-                content: { type: 'string' },
-                description: { type: 'string' },
-                type_ids: { type: 'array', items: { type: 'number' }},
-                tag_ids: { type: 'array', items: { type: 'number' }}
-              },
-              required: ['title', 'author', 'content']
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // 调用 AI 并处理结果
-  const aiResult = await callAI(requestJson)
-  const blogList = aiResult.blog_list
-
-  for (const blog of blogList) {
-    const saveData = {
-      ...blog,
-      image_url: writeInfo.cover.url,
-      image_alt: writeInfo.cover.url,
-      status: 1,
-      plate_id: 1
-    }
-    const saveResponse = await apiClient(api.saveBlog, 'POST', saveData)
-    const newBlogId = JSON.parse(saveResponse).data
-
-    // 为新创建的博客自动排队一个 SEO 任务
-    addTask('seo', { id: newBlogId, title: blog.title, type: 'blog' }, work.port)
-    broadcastMessage(createMessage('seo', `"${blog.title}" 已创建并加入 SEO 队列。`))
-  }
-
-  // 原来的代码:
-  // const successMsg = `${writeInfo.number} 篇文章已成功生成。`
-  // broadcastMessage(createMessage('write', successMsg, {}, blogList))
-  // work.port.postMessage(createMessage('success', '文章生成完成。'))
-
-  // 修改为:
-  const successMsg = `${writeInfo.number} 篇文章已成功生成。`
-  return {
-    type: 'write',
-    message: successMsg,
-    response: blogList,
-    port: work.port,
-    portMessage: createMessage('success', '文章生成完成。')
-  }
-}
-
-/**
- * 执行一个 'seo' 优化任务。
- * @param {object} work - SEO 任务的工作项。
- */
-async function doSeoTask(work) {
-  const { type, id } = work.data
-  console.log('调用接口',`get${capitalize(type)}`)
-  console.log('调用接口',api[`get${capitalize(type)}`])
-  const pageData = (await apiClient(api[`get${capitalize(type)}`], 'GET', { id })).data
-  const title = pageData.page_name || pageData.title
-
-  // 并行获取相关数据(标签、类型等)
-  const lists = {}
-  const promises = []
-  if (type !== 'staticPage') {
-    promises.push(apiClient(api[`get${capitalize(type)}Tags`], 'GET', { page: 1, page_size: 1000 }).then(r => {
-      lists.tags = r.data.data
-    }))
-    promises.push(apiClient(api[`get${capitalize(type)}Types`], 'GET', { page: 1, page_size: 1000 }).then(r => { lists.types = r.data.data }))
-  }
-  if (type === 'blog') {
-    promises.push(apiClient(api.getBlogPlates, 'GET', { page: 1, page_size: 1000 }).then(r => { lists.plates = r.data.data }))
-  }
-  await Promise.all(promises)
-
-  const seoData = state.seoConfig[`${type}Seo`]
-  if (!seoData) throw new Error(`未找到类型为 ${type} 的 SEO 配置`)
-
-  // 构建 AI 提示
-  const requestJson = {
-    contents: [{
-      role: 'model',
-      parts: [{
-        text: `
-          无论用户使用何种语言提问,返回给用户的数据一律使用${state.settings.language}回答。
-          你需要处理用户发送的数据,将数据中的'descData','keywordData','titleData','urlData' 四项根据用户的要求生成字符串后返回给用户。这些数据将被使用在用户的网站元信息中用于SEO优化。
-          descData的内容参考提示${seoData.descData[0].value}进行生成,
-          keywordData的内容参考提示${seoData.keywordData[0].value}进行生成,
-          titleData的内容参考提示${seoData.titleData[0].value}进行生成,
-          urlData的内容参考提示${seoData.urlData[0].value}进行生成,注意此项需要作为地址参数使用,不能使用特殊字符,只可使用小写字母与'-'。
-          需要SEO优化的文章具体内容数据为${JSON.stringify(pageData)};
-          类型和标签数据为id数组,其具体内容可在此处取得${JSON.stringify(lists)}。
-        `
-      }]
-    }, {
-      role: 'user',
-      parts: [{ text: '生成数据' }]
-    }],
-    generationConfig: {
-      responseMimeType: 'application/json',
-      responseSchema: {
-        type: 'object',
-        properties: {
-          descData: { type: 'string' },
-          urlData: { type: 'string' },
-          keywordData: { type: 'string' },
-          titleData: { type: 'string' }
-        },
-        required: ['descData', 'keywordData', 'titleData', 'urlData']
-      }
-    }
-  }
-
-  // 调用 AI 并保存结果
-  const aiResult = await callAI(requestJson)
-  pageData.seo_data.seo_describe = aiResult.descData
-  pageData.seo_data.seo_keyword = aiResult.keywordData
-  pageData.seo_data.seo_title = aiResult.titleData
-  pageData.seo_data.urla = aiResult.urlData
-
-  const saveResponse = await apiClient(api[`save${capitalize(type)}`], 'POST', pageData)
-
-  // 原来的代码:
-  // const successMsg = `"${title}" 的 SEO 优化已完成。`
-  // broadcastMessage(createMessage('seo', successMsg, {}, saveResponse))
-  // work.port.postMessage(createMessage('success', successMsg, {}, saveResponse))
-
-  // 修改为:
-  const successMsg = `"${title}" 的 SEO 优化已完成。`
-  return {
-    type: 'seo',
-    message: successMsg,
-    response: saveResponse,
-    port: work.port,
-    portMessage: createMessage('success', successMsg, {}, saveResponse)
-  }
-}
-
-/**
- * 执行一个 'translate' 翻译任务。
- * @param {object} work - 翻译任务的工作项。
- */
-async function doTranslateTask(work) {
-  const { type, id } = work.data
-  const pageData = (await apiClient(api[`get${capitalize(type)}`], 'GET', { id })).data
-  const title = pageData.page_name || pageData.title
-
-  // 1. 更新状态为“翻译中”
-  await apiClient(api.transStatus, 'POST', { id: pageData.seo_id, trans_status: 1 })
-
-  // 2. 构建 AI 提示并调用 AI
-  const requestJson = {
-    contents: [{
-      role: 'model',
-      parts: [{
-        text: `
-          无论用户使用何种语言提问,返回给用户的数据一律使用${state.settings.language}回答。
-          将用户提供的JSON数据翻译成${state.settings.language}并以纯json字符串的形式输出,不要输出任何markdown格式,输出纯字符串。
-        `
-      }]
-    }, {
-      role: 'user',
-      parts: [{
-        text: `翻译数据${JSON.stringify(pageData)}`
-      }]
-    }],
-    generationConfig: {
-      responseMimeType: 'text/plain' // AI 将返回一个字符串化的 JSON
-    }
-  }
-
-  const translatedText = await callAI(requestJson, true) // rawText = true
-  const translatedData = JSON.parse(translatedText)
-  if (translatedData.content === '') {
-    translatedData.content = translatedData.title
-  }
-
-  // 3. 保存翻译后的数据
-  const saveResponse = await apiClient(api[`save${capitalize(type)}`], 'POST', translatedData)
-
-  // 4. 更新状态为“已翻译”
-  await apiClient(api.transStatus, 'POST', { id: pageData.seo_id, trans_status: state.settings.language })
-
-  // 原来的代码:
-  // const successMsg = `"${title}" 翻译成功。`
-  // broadcastMessage(createMessage('translate', successMsg, {}, saveResponse))
-  // work.port.postMessage(createMessage('success', successMsg, { refresh: true, type: work.data.type }, work.data))
-
-  // 修改为:
-  const successMsg = `"${title}" 翻译成功。`
-  return {
-    type: 'translate',
-    message: successMsg,
-    response: saveResponse,
-    port: work.port,
-    portMessage: createMessage('success', successMsg, { refresh: true, type: work.data.type }, work.data)
-  }
-}
-
-// ===================================================================================
-// 5. API 及 AI 客户端
-// ===================================================================================
-
-/**
- * 一个健壮的、用于应用后端的异步 API 客户端。
- * @param {string} url - 请求的完整 URL。
- * @param {'GET'|'POST'} method - HTTP 方法。
- * @param {object} [data] - 要发送的数据。对于 GET 请求会转换为查询参数,对于 POST 请求会作为 JSON 主体。
- * @returns {Promise<any>} - 一个解析为响应数据的 Promise。
- * @throws {Error} 如果请求失败或返回非 200 状态码。
- */
-async function apiClient(url, method, data) {
-  if (!state.token) throw new Error('API 客户端调用失败:认证令牌未设置。')
-
-  const options = {
-    method: method.toUpperCase(),
-    headers: {
-      token: state.token,
-      'Content-Type': 'application/json',
-      'X-Requested-With': 'XMLHttpRequest'
-    }
-  }
-
-  let fullUrl = url
-  if (method.toUpperCase() === 'GET' && data) {
-    fullUrl += '?' + new URLSearchParams(data).toString()
-  } else if (method.toUpperCase() === 'POST' && data) {
-    options.body = JSON.stringify(data)
-  }
-
-  const response = await fetch(fullUrl, options)
-
-  if (!response.ok) {
-    const errorBody = await response.text()
-    throw new Error(`API 错误: ${response.status} ${response.statusText} on ${url}. Body: ${errorBody}`)
-  }
-  // 原始 API 在某些情况下似乎返回 JSON 字符串,因此我们返回原始响应的文本
-  // 以便进行灵活的解析 (.json() 或 .text())
-  return response.json()
-}
-
-/**
- * 使用给定的负载调用 AI 服务。
- * @param {object} payload - 发送给 AI 请求的 JSON 负载。
- * @param {boolean} [expectRawText=false] - 如果为 true,则返回原始文本响应。如果为 false,则解析内部的 JSON。
- * @returns {Promise<any>} - 一个解析为 AI 响应数据的 Promise。
- * @throws {Error} 如果 AI 调用失败或响应格式错误。
- */
-async function callAI(payload, expectRawText = false) {
-  const response = await fetch(state.aiUrl, {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json' },
-    body: JSON.stringify(payload)
-  })
-
-  if (!response.ok) {
-    const errorBody = await response.text()
-    throw new Error(`AI API 错误: ${response.status} ${response.statusText}. Body: ${errorBody}`)
-  }
-
-  const responseData = await response.json()
-  const textPart = responseData?.candidates?.[0]?.content?.parts?.[0]?.text
-  if (typeof textPart !== 'string') {
-    throw new Error('AI 响应格式无效:缺少文本部分或非字符串类型。')
-  }
-
-  if (expectRawText) {
-    return textPart
-  }
-
-  try {
-    // AI 有时会将其 JSON 输出包裹在 markdown 的 ```json ... ``` 中,因此我们需要清理它。
-    const cleanedText = textPart.replace(/^```json\s*|```\s*$/g, '')
-    return JSON.parse(cleanedText)
-  } catch (e) {
-    console.error('从 AI 响应中解析内部 JSON 失败:', textPart)
-    throw new Error('AI 返回了格式错误的 JSON 字符串。')
-  }
-}
-
-// ===================================================================================
-// 6. 工具函数
-// ===================================================================================
-
-/**
- * 创建一个标准的消息对象,用于通过端口发送。
- * @param {string} type - 消息类型。
- * @param {string} message - 主要的消息文本。
- * @param {object} data - 任何要发送的附加数据。
- * @param {any} [debugCode] - 调试信息。
- * @returns {object} 结构化的消息对象。
- */
-function createMessage(type, message, data = {}, debugCode = {}) {
-  return {
-    type: state.settings.debug ? 'code' : type,
-    message: message,
-    time: Date.now(),
-    code: state.settings.debug ? debugCode : '',
-    data: {
-      ...data,
-      workLength: state.workList.length,
-      isInit: !!state.mainPort
-    }
-  }
-}
-
-/**
- * 如果主端口和助手端口存在,则向它们广播消息。
- * @param {object} messageObject - 由 createMessage 创建的消息对象。
- */
-function broadcastMessage(messageObject) {
-  if (state.mainPort) {
-    state.mainPort.postMessage(messageObject)
-  }
-  if (state.helperPort) {
-    // 向助手页面发送一个简化的成功/失败消息
-    const helperMsgType = (messageObject.type === 'error' || messageObject.type === 'warning') ? messageObject.type : 'success'
-    state.helperPort.postMessage(createMessage(helperMsgType, messageObject.message, {}, messageObject.code))
-  }
-}
-
-/**
- * 将字符串的第一个字母大写。
- * 例如:'blog' -> 'Blog'
- * @param {string} s - 要大写的字符串。
- * @returns {string}
- */
-function capitalize(s) {
-  if (typeof s !== 'string' || !s) return s
-  return s.charAt(0).toUpperCase() + s.slice(1)
-}
-
-/**
- * 从模板生成封面图片 URL。
- * 注意:此函数存在于原始文件中但未使用。
- * 它依赖于一个此处未定义的 'md5' 函数。
- * @param {string} title - 要嵌入图片中的标题。
- * @param {string} image - 背景图片 URL。
- * @returns {string} - 最终的模板化图片 URL。
- */
-function getTemplateImage(title, image) {
-  // 此函数需要一个全局可用的 'md5' 函数。
-  if (typeof md5 !== 'function') {
-    console.error('getTemplateImage 需要一个 md5() 函数,但该函数未定义。')
-    return ''
-  }
-  const urlPrefix = 'https://image.edgeone.app'
-  const format = 'png'
-  const userId = '3e5b976c108b4febb15687047013beff'
-  const templateId = 'ep-uchZw5e0Lxe9'
-  const apiKey = 'rC5asgUt51is'
-  const params = { image, title }
-
-  const sortedKeys = Object.keys(params).sort()
-  const searchParams = sortedKeys.map(key => `${key}=${params[key]}`).join('&')
-  const signData = JSON.stringify({ apiKey, searchParams })
-  const sign = md5(signData)
-  const encodedSearchParams = sortedKeys.map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')
-
-  return `${urlPrefix}/${sign}/${userId}/${templateId}.${format}?${encodedSearchParams}`
-}

+ 79 - 21
src/views/invitationManage/edit.vue

@@ -4,7 +4,7 @@ import domtoimage from 'dom-to-image-more'
 import * as monaco from 'monaco-editor'
 import { upload } from '@/api/system'
 import { saveTemplate } from '@/api/template'
-
+import { getExpoList } from '@/api/expo'
 export default Vue.extend({
   name: 'Index',
   components: {
@@ -82,19 +82,28 @@ export default Vue.extend({
     </tbody>
 </table>`,
       viewCode: '',
-      exhibitorSetting: {
-        name: '测试名称',
-        startTime: '测试时间',
-        endTime: '测试时间',
-        address: '测试地址',
-        exhibitorName: '测试数据',
-        telephone: '测试电话',
-        email: '测试邮件',
-        socialMedia: [],
-        mainPicture: '/static/image/cover.webp',
-        logo: '/static/image/avatar.webp',
-        description: ''
+      exhibitorSetting: {},
+      userSetting: {
+        first_name: '张',
+        last_name: '三',
+        full_name: '张三',
+        id_type: '中国居民身份证',
+        id_number: '430111200001011111',
+        mobile: '18888888888',
+        mobile_country_code: '+86',
+        email: 'zhansan@gmail.com',
+        company: '北京某某有限公司',
+        department: '销售部门',
+        position: '客户专员',
+        country: '中国',
+        province: '北京市',
+        city: '北京',
+        address: '烟袋斜街',
+        interested_products: '预制菜',
+        business_type: '销售'
       },
+      expoList: [],
+      expoId: 0,
       timer: null,
       editor: null,
       templateInfo: {
@@ -108,21 +117,36 @@ export default Vue.extend({
   },
   mounted() {
     this.init()
-    this.initEditor()
-    this.parseCode()
   },
   methods: {
     init() {
       if (this.$route.params.id) {
         this.loading = true
         this.templateInfo.id = this.$route.params.id
-        let templateInfo = JSON.parse(sessionStorage.getItem('invitationInfo'))
+        const templateInfo = JSON.parse(sessionStorage.getItem('invitationInfo'))
         this.templateInfo.name = templateInfo.name
         this.templateInfo.description = templateInfo.description
         this.code = templateInfo.content
         console.log(templateInfo)
         this.loading = false
       }
+      getExpoList(1, 1000).then(res => {
+        this.expoList = res.data.data
+        for (var i = 0; i < this.expoList.length; i++) {
+          if (this.expoList[i].status === 0) {
+            this.expoId = this.expoList[i].id
+            this.exhibitorSetting = this.expoList[i]
+            break
+          }
+        }
+        this.initEditor()
+        this.parseCode()
+      }).catch(err => {
+        this.loading = false
+      })
+    },
+    changeExpo(event) {
+      console.log(event)
     },
     saveTemp() {
       if (this.loading) { return }
@@ -137,9 +161,10 @@ export default Vue.extend({
           }
           const picFile = new File([u8arr], 'invitation.png', { type: mime })
           upload(picFile).then(res => {
-            saveTemplate(this.templateInfo.id, this.templateInfo.name, this.templateInfo.description, this.code , res.data.file)
+            saveTemplate(this.templateInfo.id, this.templateInfo.name, this.templateInfo.description, this.code, res.data.file)
               .then(res => {
                 console.log(res)
+                this.$router.push('/invitation/list')
                 this.loading = false
               }).catch(err => {
                 this.loading = false
@@ -159,6 +184,9 @@ export default Vue.extend({
           if (Object.prototype.hasOwnProperty.call(this.exhibitorSetting, trimmedKey)) {
             return this.exhibitorSetting[trimmedKey]
           }
+          if (Object.prototype.hasOwnProperty.call(this.userSetting, trimmedKey)) {
+            return this.userSetting[trimmedKey]
+          }
           return match
         })
         this.viewCode = processedCode
@@ -195,7 +223,22 @@ export default Vue.extend({
                 }
               }
             })
-            return { suggestions }
+            const suggestions_2 = Object.keys(this.userSetting).map(key => {
+              return {
+                label: key,
+                kind: monaco.languages.CompletionItemKind.Field,
+                insertText: key,
+                detail: `插入用户信息: ${key}`,
+                range: {
+                  startLineNumber: position.lineNumber,
+                  startColumn: position.column,
+                  endLineNumber: position.lineNumber,
+                  endColumn: position.column
+                }
+              }
+            })
+            console.log(this.userSetting, this.exhibitorSetting)
+            return { suggestions: [...suggestions, ...suggestions_2] }
           }
         })
       }
@@ -221,9 +264,15 @@ export default Vue.extend({
 </script>
 
 <template>
-  <div class="main-box" v-loading="loading">
+  <div v-loading="loading" class="main-box">
     <div class="head">
-      <el-input v-model="templateInfo.name" class="name" />
+      <div class="head-left">
+        <el-input v-model="templateInfo.name" class="name" />
+        <el-select v-model="expoId" @change="changeExpo">
+          <el-option v-for="expo in expoList" :key="expo.id" :label="expo.expo_name" :value="expo.id" />
+        </el-select>
+      </div>
+
       <el-popover
         popper-class="popover"
         placement="left-start"
@@ -294,15 +343,24 @@ export default Vue.extend({
         left: 0;
         height: 100%;
         width: 100%;
+        div{
+          background: white;
+        }
       }
     }
   }
   .head{
     display: flex;
     justify-content: space-between;
-    .name{
+    .head-left{
       width: 50%;
+      display: flex;
+      gap: 8px;
+      .name{
+        flex: 1;
+      }
     }
+
   }
 }
 </style>

+ 3 - 1
src/views/login/index.vue

@@ -40,10 +40,12 @@ export default Vue.extend({
       this.$store.dispatch('login', {
         username: this.username,
         password: this.password,
+        login_type: 0,
+        login_portal: 0,
         savePassword: this.savePassword
       }).then(res => {
         this.loading = false
-        this.$router.push('/')
+        this.$router.push('/dashboard')
       }).catch(err => {
         this.loading = false
         this.$message.error(err.message)

+ 64 - 48
src/views/preRegManage/edit.vue

@@ -19,7 +19,7 @@ export default Vue.extend({
           name: 'first_name',
           label: '请在下方输入名字',
           value: '',
-          width: 1,
+          width: 3,
           placeholder: '请输入名字',
           required: false
         },
@@ -28,7 +28,7 @@ export default Vue.extend({
           name: 'last_name',
           label: '请在下方输入姓氏',
           value: '',
-          width: 1,
+          width: 3,
           placeholder: '请输入姓氏',
           required: false
         },
@@ -37,10 +37,10 @@ export default Vue.extend({
           name: 'id_type',
           label: '请在下方选择证件类型',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '身份证', value: 'id' },
-            { label: '护照', value: 'passport' },
+            { label: '护照', value: 'passport' }
           ],
           placeholder: '请选择',
           required: false
@@ -50,26 +50,36 @@ export default Vue.extend({
           name: 'id_number',
           label: '请在下方输入证件号码',
           value: '',
-          width: 1,
+          width: 4,
           placeholder: '请输入证件号码',
           required: false
         },
         {
           type: 'phone',
+          name: 'mobile_country_code',
+          label: '请在下方输入手机国家代码',
+          value: '',
+          country: 'CN',
+          width: 2,
+          placeholder: '请输入手机号',
+          required: false
+        },
+        {
+          type: 'input',
           name: 'mobile',
           label: '请在下方输入手机号码',
           value: '',
           country: 'CN',
-          width: 1,
+          width: 4,
           placeholder: '请输入手机号',
           required: false
         },
         {
-          type: 'email',
+          type: 'input',
           name: 'email',
           label: '请在下方输入邮箱',
           value: '',
-          width: 1,
+          width: 6,
           placeholder: '请输入邮箱',
           codePlaceholder: '请输入验证码',
           required: false
@@ -79,7 +89,7 @@ export default Vue.extend({
           name: 'company',
           label: '请在下方输入公司名称',
           value: '',
-          width: 6,
+          width: 2,
           placeholder: '请输入公司名称',
           required: false
         },
@@ -88,7 +98,7 @@ export default Vue.extend({
           name: 'department',
           label: '请在下方输入部门名称',
           value: '',
-          width: 1,
+          width: 2,
           placeholder: '请输入部门名称',
           required: false
         },
@@ -97,7 +107,7 @@ export default Vue.extend({
           name: 'position',
           label: '请在下方选择职位',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '职位', value: '职位' }
           ],
@@ -109,7 +119,7 @@ export default Vue.extend({
           name: 'country',
           label: '请在下方选择国家',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '国家', value: '国家' }
           ],
@@ -121,7 +131,7 @@ export default Vue.extend({
           name: 'province',
           label: '请在下方选择省份',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '省份', value: '省份' }
           ],
@@ -133,7 +143,7 @@ export default Vue.extend({
           name: 'city',
           label: '请在下方选择市',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '市', value: '市' }
           ],
@@ -145,7 +155,7 @@ export default Vue.extend({
           name: 'address',
           label: '请在下方输入地址',
           value: '',
-          width: 1,
+          width: 6,
           placeholder: '请输入地址',
           required: false
         },
@@ -154,13 +164,31 @@ export default Vue.extend({
           name: 'industry',
           label: '请在下方选择行业',
           value: '',
-          width: 1,
+          width: 2,
           options: [
             { label: '行业', value: '行业' }
           ],
           placeholder: '请选择',
           required: false
         },
+        {
+          type: 'input',
+          name: 'interested_products',
+          label: '感兴趣的产品',
+          value: '',
+          width: 3,
+          placeholder: '请输入地址',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'business_type',
+          label: '业务性质',
+          value: '',
+          width: 3,
+          placeholder: '请输入地址',
+          required: false
+        },
       ],
       formData: [],
       trashData: [],
@@ -184,18 +212,15 @@ export default Vue.extend({
   methods: {
     init() {
       function getType(name) {
-        if(['first_name','last_name','id_number','company','department','address'].includes(name)) {
+        if (['first_name', 'last_name', 'email' ,'id_number', 'company', 'department', 'address','country','interested_products','business_type','mobile'].includes(name)) {
           return 'input'
         }
-        if(['id_type','position','province','city','industry'].includes(name)) {
+        if (['id_type', 'position', 'province', 'city', 'industry'].includes(name)) {
           return 'select'
         }
-        if(['mobile'].includes(name)) {
+        if (['mobile_country_code'].includes(name)) {
           return 'phone'
         }
-        if(['email'].includes(name)) {
-          return 'email'
-        }
       }
       if (this.$route.params.id) {
         this.loading = true
@@ -204,17 +229,20 @@ export default Vue.extend({
           this.loading = false
           this.formInfo.name = res.data.template_name
           this.formInfo.desc = res.data.description
-          res.data.fields.forEach(item => {
-            console.log(item)
-            this.formData.push({
-              key: item.id,
-              type: getType(item.field_name),
-              name: item.field_name,
-              label: item.field_label,
-              value: '',
-              width: 1,
-              placeholder: '请输入',
-              required: item.is_required
+          res.data.fields.forEach(fields => {
+            this.compList.forEach(item => {
+              if (fields.field_name === item.name) {
+                this.formData.push({
+                  key: fields.id,
+                  type: getType(fields.field_name),
+                  name: fields.field_name,
+                  label: fields.field_label,
+                  value: '',
+                  width: item.width,
+                  placeholder: '请输入',
+                  required: fields.is_required ? 1 : 0
+                })
+              }
             })
           })
           console.log(res)
@@ -233,7 +261,7 @@ export default Vue.extend({
         saveData.push({
           field_name: item.name,
           field_label: item.label,
-          is_required: item.required,
+          is_required: item.required? 1 : 0
         })
       })
       saveForm(this.formInfo.id, this.formInfo.name, this.formInfo.desc, saveData).then(res => {
@@ -322,23 +350,12 @@ export default Vue.extend({
                 <div class="tips">{{ element.label }}</div>
                 <el-input :value="element.value" :placeholder="element.placeholder" />
               </div>
-              <div v-if="element.type==='email'" :class="[element.type,'form-item']">
-                <div class="tips">{{ element.label }}</div>
-                <div>
-                  <el-input v-model="element.value" :placeholder="element.placeholder" />
-                </div>
-                <div class="code-input">
-                  <el-input v-model="element.value" :placeholder="element.codePlaceholder" />
-                  <el-button>获取验证码</el-button>
-                </div>
-              </div>
               <div v-if="element.type==='phone'" :class="[element.type,'form-item']">
                 <div class="tips">{{ element.label }}</div>
                 <div class="phone-input">
                   <el-select v-model="element.country" :placeholder="element.placeholder">
                     <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.country_code" />
                   </el-select>
-                  <el-input v-model="element.value" :placeholder="element.placeholder" />
                 </div>
               </div>
               <div v-if="element.type==='select'" :class="[element.type,'form-item']">
@@ -382,7 +399,6 @@ export default Vue.extend({
                     <el-select v-model="element.country" :placeholder="element.placeholder">
                       <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.country_code" />
                     </el-select>
-                    <el-input v-model="element.value" :placeholder="element.placeholder" />
                   </div>
                 </div>
                 <div v-if="element.type==='select'" :class="[element.type,'form-item','view',{'active':element.key===hoverKey||element.key===currentKey}]">
@@ -485,7 +501,7 @@ export default Vue.extend({
         }
       }
       &.phone{
-        .phone-input{
+        .el-select{
           display: flex;
           grid-gap: 8px;
         }
@@ -666,7 +682,7 @@ export default Vue.extend({
           .form-body{
             .drag-cont{
               display: grid;
-              grid-template-columns: 1fr;
+              grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
               width: 100%;
               min-height: 300px;
               align-content: start;

+ 14 - 20
src/views/preRegManage/list.vue

@@ -1,8 +1,8 @@
 <script>
 import Vue from 'vue'
-import { getFormList,setFormStatus,deleteForm } from '@/api/form'
+import { getFormList, setFormStatus, deleteForm } from '@/api/form'
 export default Vue.extend({
-  name: "index",
+  name: 'Index',
   data() {
     return {
       formList: [],
@@ -12,7 +12,7 @@ export default Vue.extend({
       page_size: 20,
       searchWord: '',
       searchTimer: null,
-      loading: false,
+      loading: false
     }
   },
   mounted() {
@@ -32,18 +32,15 @@ export default Vue.extend({
       }, 500)
     },
     edit(row) {
-      console.log(row)
       this.$router.push({ path: '/preRegister/edit/' + row.id })
     },
     setStatus(row) {
       this.loading = true
       setFormStatus(row.id, row.status ? 0 : 1).then(res => {
         this.loading = false
-        console.log(res)
         this.refresh()
       }).catch(err => {
         this.loading = false
-        console.log(err)
       })
     },
     del(row) {
@@ -58,7 +55,6 @@ export default Vue.extend({
               this.refresh()
             }).catch(err => {
               this.loading = false
-              console.log(err)
             })
           }
         }
@@ -68,7 +64,6 @@ export default Vue.extend({
       if (this.loading || this.current_page > this.last_page) return
       this.loading = true
       getFormList(this.current_page, this.page_size, this.searchWord).then(res => {
-        console.log(res)
         this.current_page = res.data.current_page
         this.last_page = res.data.last_page
         this.total = res.data.total
@@ -80,7 +75,6 @@ export default Vue.extend({
       if (this.loading || this.current_page >= this.last_page) return
       this.loading = true
       getFormList(++this.current_page, this.page_size, this.searchWord).then(res => {
-        console.log(res)
         this.current_page = res.data.current_page
         this.last_page = res.data.last_page
         this.total = res.data.total
@@ -95,7 +89,7 @@ export default Vue.extend({
 <template>
   <div class="main-box">
     <div class="head">
-      <el-input v-model="searchWord" @input="search" prefix-icon="el-icon-search" placeholder="搜索表单名称" class="input">
+      <el-input v-model="searchWord" prefix-icon="el-icon-search" placeholder="搜索表单名称" class="input" @input="search">
         <el-button v-if="searchWord" slot="append" icon="el-icon-delete" @click="searchWord='';search()" />
       </el-input>
       <el-button icon="el-icon-plus" type="primary" @click="handleCreate">创建表单</el-button>
@@ -107,8 +101,8 @@ export default Vue.extend({
           :show-overflow-tooltip="true"
           label="表单名称"
           prop="template_name"
-          width="300">
-        </el-table-column>
+          width="300"
+        />
         <el-table-column
           label="状态"
           width="80"
@@ -124,23 +118,23 @@ export default Vue.extend({
           label="表单描述"
           prop="description"
           width="300"
-        >
-        </el-table-column>
+        />
         <el-table-column
-          label="数量">
-        </el-table-column>
+          label="数量"
+        />
         <el-table-column
           label="最后编辑"
           prop="update_time"
-          width="200">
-        </el-table-column>
+          width="200"
+        />
         <el-table-column
           label="操作"
           fixed="right"
-          width="200">
+          width="200"
+        >
           <template slot-scope="scope">
             <span class="button" @click="edit(scope.row)">编辑</span>
-            <span class="button" @click="setStatus(scope.row)">{{scope.row.status?'启用':'禁用'}}</span>
+            <span class="button" @click="setStatus(scope.row)">{{ scope.row.status?'启用':'禁用' }}</span>
             <span class="button del" @click="del(scope.row)">删除</span>
           </template>
         </el-table-column>

+ 53 - 10
src/views/setting/systemSetting.vue

@@ -1,15 +1,52 @@
 <script>
 import Vue from 'vue'
-
+import { getMailSettings, saveMailSetting } from '@/api/system'
 export default Vue.extend({
-  name: "systemSetting"
+  name: 'SystemSetting',
+  data() {
+    return {
+      mailSetting: {
+        from_name: 'admin',
+        from_email: '',
+        auth_code: '',
+        smtp_server: '',
+        smtp_port: '',
+        is_ssl: 0
+      },
+      loading: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init() {
+      this.loading = true
+      getMailSettings().then(res => {
+        this.loading = false
+        if (res.data) {
+          this.mailSetting = res.data
+        }
+      })
+    },
+    save() {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      saveMailSetting(this.mailSetting.id,this.mailSetting.from_name,this.mailSetting.from_email,this.mailSetting.auth_code,this.mailSetting.smtp_server,this.mailSetting.smtp_port,this.mailSetting.is_ssl).then(res => {
+        this.loading = false
+        this.init()
+      })
+    }
+  }
 })
 </script>
 
 <template>
-  <div class="main-box">
+  <div v-loading="loading" class="main-box">
     <div class="save">
-      <el-button type="primary">保存</el-button>
+      <el-button @click="save" type="primary">保存</el-button>
     </div>
     <div class="scroll">
       <div class="setting-box">
@@ -19,25 +56,31 @@ export default Vue.extend({
             <div class="label">
               发件邮箱地址
             </div>
-            <el-input placeholder="请输入发件邮箱地址"></el-input>
+            <el-input v-model="mailSetting.from_email" placeholder="请输入发件邮箱地址" />
           </div>
           <div class="setting-item">
             <div class="label">
               发件邮箱密码/授权码
             </div>
-            <el-input placeholder="请输入发件邮箱密码/授权码"></el-input>
+            <el-input v-model="mailSetting.auth_code" placeholder="请输入发件邮箱密码/授权码" />
           </div>
           <div class="setting-item">
             <div class="label">
               SMTP服务器地址
             </div>
-            <el-input placeholder="请输入SMTP服务器地址"></el-input>
+            <el-input v-model="mailSetting.smtp_server" placeholder="请输入SMTP服务器地址" />
           </div>
           <div class="setting-item">
             <div class="label">
               SMTP端口
             </div>
-            <el-input placeholder="请输入SMTP端口"></el-input>
+            <el-input v-model="mailSetting.smtp_port" placeholder="请输入SMTP端口" />
+          </div>
+          <div class="setting-item">
+            <div class="label">
+              启用SSL
+            </div>
+            <el-switch v-model="mailSetting.is_ssl" />
           </div>
         </div>
       </div>
@@ -50,9 +93,9 @@ export default Vue.extend({
             <div class="label">
               接收通知邮箱
             </div>
-            <el-input placeholder="请输入通知邮箱"></el-input>
+            <el-input placeholder="请输入通知邮箱" />
           </div>
-          <div class="setting-item"></div>
+          <div class="setting-item" />
           <div class="setting-item">
             <el-button type="primary">发送测试邮件</el-button>
           </div>

+ 128 - 0
src/views/user/form.vue

@@ -0,0 +1,128 @@
+<script lang="ts">
+import Vue from 'vue'
+import { getFormInfo } from '@/api/form'
+export default Vue.extend({
+  name: 'Index',
+  data() {
+    return {
+      from_data: [],
+      form_id: ''
+    }
+  },
+  computed: {
+    user() { return this.$store.state.user.user },
+    token() { return this.$store.state.user.token }
+  },
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init() {
+      if (this.token) {
+        this.form_id = this.$route.query.id
+        getFormInfo(this.form_id).then(res => {
+          this.from_data = res.data
+        }).catch(err => {
+          console.log('err')
+        })
+      } else {
+        this.$router.push({
+          name: 'userRegister',
+          query: {
+            form_id: this.$route.query.id,
+            expo_key: this.$route.query.expo
+          }
+        })
+      }
+    },
+    returnWidth(name) {
+      if (['email', 'address'].includes(name)) {
+        return 6
+      }
+      if (['id_number', 'mobile'].includes(name)) {
+        return 4
+      }
+      if (['first_name', 'last_name', 'interested_products', 'business_type'].includes(name)) {
+        return 3
+      }
+      return 2
+    }
+  }
+})
+</script>
+
+<template>
+  <div class="body">
+    <div class="head">
+      <div class="form-name">
+        {{ from_data.template_name }}
+      </div>
+    </div>
+    <div class="form">
+      <div class="desc">
+        {{ from_data.description }}
+      </div>
+      <div class="form-body">
+        <div v-for="item in from_data.fields" :key="item.id" :style="{gridColumn:'span '+returnWidth(item.field_name)}" class="form-item">
+          <div class="label" :class="item.required?'required':''">{{ item.field_label }}</div>
+          <el-input v-model="item.value"></el-input>
+        </div>
+      </div>
+      <div class="button">
+        <el-button type="primary">提交表单</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.body{
+  margin: 36px auto;
+  max-width: 800px;
+  width: 100%;
+  .form{
+    border-radius: 8px;
+    margin-top: 16px;
+    padding: 24px;
+    box-shadow: 0 0 8px 0 #00000008;
+    background: white;
+    .button{
+      margin-top: 12px;
+      display: flex;
+      justify-content: flex-end;
+    }
+    .desc{
+      color: gray;
+      margin-bottom: 32px;
+    }
+    .form-body{
+      display: grid;
+      grid-template-columns: repeat(6, 1fr);
+      grid-gap: 8px;
+      .form-item{
+        .label{
+          font-size: 15px;
+          color: gray;
+          margin-bottom: 8px;
+        }
+      }
+    }
+  }
+  .head{
+    padding: 0 8px;
+    display: flex;
+    gap: 16px;
+    align-items: flex-end;
+    .form-name{
+      margin-bottom: 8px;
+      font-size: 24px;
+      font-weight: 600;
+    }
+  }
+}
+</style>
+<style>
+body{
+  background: #F9FAFB;
+}
+</style>

+ 273 - 0
src/views/user/register.vue

@@ -0,0 +1,273 @@
+<script lang="ts">
+import Vue from 'vue'
+import countryCode from '@/lib/countryCode.json'
+import { sendSmsCode, sentEmailCode, register } from '@/api/user'
+export default Vue.extend({
+  name: 'Index',
+  data() {
+    return {
+      regWay: 0, // 0:手机注册 1:邮箱注册
+      countryCode,
+      loginInfo: {
+        phone: '',
+        countryCode: '86',
+        smsCode: '',
+        email: '',
+        emailCode: ''
+      },
+      emailCounter: 0,
+      emailTimer: null,
+      smsCounter: 0,
+      smsTimer: null
+    }
+  },
+  computed: {
+    user() { return this.$store.state.user.user }
+  },
+  mounted() {
+
+  },
+  methods: {
+    sentSms() {
+      if (this.loginInfo.phone === '') {
+        this.$message.error('请填写电话号码!')
+        return
+      }
+      if (this.smsTimer) {
+        return
+      } else {
+        sendSmsCode(this.loginInfo.phone, this.loginInfo.countryCode).then(res => {
+          this.$message.success('发送成功!')
+          this.smsCounter = 60
+          this.smsTimer = setInterval(() => {
+            this.smsCounter--
+            if (this.smsCounter <= 0) {
+              clearInterval(this.smsTimer)
+              this.smsTimer = null
+            }
+          }, 1000)
+        }).catch(err => {
+        })
+      }
+    },
+    sentEmail() {
+      if (this.loginInfo.email === '') {
+        this.$message.error('请填写邮箱!')
+        return
+      }
+      if (this.emailTimer) {
+        return
+      } else {
+        sentEmailCode(this.loginInfo.email).then(res => {
+          this.$message.success('发送成功!')
+          this.emailCounter = 60
+          this.emailTimer = setInterval(() => {
+            this.emailCounter--
+            if (this.emailCounter <= 0) {
+              clearInterval(this.emailTimer)
+              this.emailTimer = null
+            }
+          }, 1000)
+        }).catch(err => {
+        })
+      }
+    },
+    register() {
+      if (this.regWay) {
+        register(this.loginInfo.email,'',this.loginInfo.emailCode,1,'').then(res=>{
+
+        }).catch(err=>{
+
+        })
+      } else {
+        register('',this.loginInfo.phone,this.loginInfo.smsCode,0,this.loginInfo.countryCode).then(res=>{
+
+        }).catch(err=>{
+
+        })
+      }
+    }
+  }
+})
+</script>
+<template>
+  <div class="body">
+    <div class="head">
+      <div class="title">
+        注册账号
+      </div>
+      <div class="desc">
+        中国大陆观众建议使用手机号码进行注册登录。如无法接收验证短信可点击下方微信
+      </div>
+    </div>
+    <div class="time-counter">
+      <div class="title">距离展览开幕时间</div>
+      <div class="counter-list">
+        <div class="counter-item">
+          <div class="num">73</div>
+          <div class="unit">天</div>
+        </div>
+        <div class="counter-item">
+          <div class="num">73</div>
+          <div class="unit">天</div>
+        </div>
+        <div class="counter-item">
+          <div class="num">73</div>
+          <div class="unit">天</div>
+        </div>
+        <div class="counter-item">
+          <div class="num">73</div>
+          <div class="unit">天</div>
+        </div>
+      </div>
+    </div>
+    <div class="register-body">
+      <div class="tab">
+        <div class="tab-item" :class="regWay?'':'active'" @click="regWay=0">手机注册</div>
+        <div class="tab-item" :class="regWay?'active':''" @click="regWay=1">邮箱注册</div>
+      </div>
+      <template v-if="regWay">
+        <div class="form-item">
+          <div class="label">电子邮箱</div>
+          <el-input v-model="loginInfo.email" placeholder="去输入电子邮箱地址" />
+        </div>
+        <div class="form-item">
+          <div class="label">验证码</div>
+          <div class="sms-cont">
+            <el-input v-model="loginInfo.emailCode" placeholder="请输入验证码" />
+            <el-button :disabled="emailTimer" type="primary" @click="sentEmail()">{{ emailTimer?emailCounter+'秒后重新获取':'获取验证码' }}</el-button>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="form-item">
+          <div class="label">
+            国家/地区
+          </div>
+          <el-select v-model="loginInfo.countryCode" placeholder="请选择国家/地区">
+            <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.phone_code" />
+          </el-select>
+        </div>
+        <div class="form-item">
+          <div class="label">手机号码</div>
+          <el-input v-model="loginInfo.phone" placeholder="请输入手机号码" />
+        </div>
+        <div class="form-item">
+          <div class="label">验证码</div>
+          <div class="sms-cont">
+            <el-input v-model="loginInfo.smsCode" placeholder="请输入验证码" />
+            <el-button :disabled="smsTimer" type="primary" @click="sentSms()">{{ smsTimer?smsCounter+'秒后重新获取':'获取验证码' }}</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="piracy">
+        注册即表示同意
+        <span>《隐私政策》</span>
+        、
+        <span>《用户协议》</span>
+      </div>
+      <el-button type="primary" @click="register()">注册</el-button>
+    </div>
+  </div>
+</template>
+<style scoped lang="scss">
+.body{
+  margin: 36px auto;
+  max-width: 800px;
+  width: 100%;
+  .register-body{
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    margin-top: 32px;
+    padding: 40px 50px;
+    border-radius: 4px;
+    box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
+    background: white;
+    .piracy{
+      color: #6B7280;
+      display: flex;
+      font-size: 16px;
+      span{
+        color: #0052CC;
+      }
+    }
+    .form-item{
+      .label{
+        color: #374151;
+        font-size: 16px;
+        margin-bottom: 8px;
+      }
+      .el-select{
+        width: 100%;
+      }
+      .sms-cont{
+        display: flex;
+        gap: 12px;
+      }
+    }
+    .tab{
+      display: flex;
+      border-bottom: 3px solid #E5E7EB;
+      .tab-item{
+        cursor: pointer;
+        padding: 8px 16px;
+        color: #6B7280;
+        transform: translateY(3px);
+        border-bottom: 3px solid transparent;
+        &.active{
+          color: #0052CC;
+          border-bottom: 3px solid #0052CC;
+        }
+      }
+    }
+  }
+  .time-counter{
+    margin-top: 32px;
+    background: #4DA9FF22;
+    padding: 24px;
+    border-radius: 8px;
+    .counter-list{
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      gap: 16px;
+      .counter-item{
+        background: white;
+        padding: 16px 0;
+        border-radius: 4px;
+        text-align: center;
+        .num{
+          color: #4D7CFF;
+          font-size: 30px;
+          font-weight: bold;
+        }
+        .unit{
+          font-size: 14px;
+          color: #6B7280;
+        }
+      }
+    }
+    .title{
+      font-size: 18px;
+      margin-bottom: 12px;
+    }
+  }
+  .head{
+    .title{
+      text-align: center;
+      font-size: 24px;
+      font-weight: bold;
+    }
+    .desc{
+      margin-top: 8px;
+      text-align: center;
+      color: #4B5563;
+    }
+  }
+}
+</style>
+<style>
+body{
+  background: #F9FAFB;
+}
+</style>