Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

yuhongqi 1 день назад
Родитель
Сommit
cd4e2b9106

+ 2 - 2
.env.prod-mengniu

@@ -1,7 +1,7 @@
 # 页面标题
-VUE_APP_TITLE =康享未来总管理系统
+VUE_APP_TITLE =总后台管理系统
 # 首页菜单标题
-VUE_APP_TITLE_INDEX =康享未来管理系统
+VUE_APP_TITLE_INDEX =总后台管理系统
 # 公司名称
 VUE_APP_COMPANY_NAME =重庆云联融智科技有限公司
 # ICP备案号

+ 9 - 3
.env.prod-sxtb

@@ -3,13 +3,13 @@ VUE_APP_TITLE =挑宝易购总管理系统
 # 首页菜单标题
 VUE_APP_TITLE_INDEX =挑宝易购
 # 公司名称
-VUE_APP_COMPANY_NAME =陕西和泰惠健科技控股集团有限公司
+VUE_APP_COMPANY_NAME =西安挑宝益康医药连锁有限公司
 # ICP备案号
-VUE_APP_ICP_RECORD =陕ICP备2025072324号
+VUE_APP_ICP_RECORD =陕ICP备17006076号-26
 # ICP网站访问地址
 VUE_APP_ICP_URL =https://beian.miit.gov.cn
 # 网站LOG
-VUE_APP_LOG_URL =@/assets/logo/sxjz.png
+VUE_APP_LOG_URL =@/assets/logo/tbyg.png
 # 存储桶配置
 VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
 # 存储桶配置
@@ -31,6 +31,9 @@ VUE_APP_VIDEO_URL = https://sxjzvolcengine.ylrztop.com
 #火山云视频点播空间名
 VUE_APP_HSY_SPACE = sxjz-2114522511
 
+#直播解码路径
+VUE_APP_LIVE_PATH = /live
+
 # 开发环境配置
 ENV = 'development'
 
@@ -42,3 +45,6 @@ VUE_APP_COURSE_DEFAULT = 1
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+#上面地址是为了解决跨越,用nginx进行了转发
+VUE_APP_LIVE_WS_URL = wss://websocket.xianhetaihuij.cn/ws

+ 26 - 0
src/api/hisStore/productActivity.js

@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 根据商品ID查询活动设置(含活动状态、是否进行中)
+export function getActivityByProductId(productId) {
+  return request({
+    url: '/store/store/productActivity/getByProductId/' + productId,
+    method: 'get'
+  })
+}
+
+// 保存活动设置(批量保存)
+export function saveActivity(data) {
+  return request({
+    url: '/store/store/productActivity/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除活动设置
+export function removeActivity(productId) {
+  return request({
+    url: '/store/store/productActivity/remove/' + productId,
+    method: 'delete'
+  })
+}

+ 241 - 0
src/api/xsy/xsy.js

@@ -0,0 +1,241 @@
+import request from '@/utils/request'
+
+/**
+ * 查询销售易账号列表
+ */
+export function listXsyAccount(query) {
+  return request({
+    url: '/xsy/account/list',
+    method: 'get',
+    params: query
+  })
+}
+
+
+
+/**
+ * 查询销售易账号详情
+ */
+export function getXsyAccount(id) {
+  return request({
+    url: '/xsy/account/get/' + id,
+    method: 'get'
+  })
+}
+
+/**
+ * 新增销售易账号
+ */
+export function addXsyAccount(data) {
+  return request({
+    url: '/xsy/account/add',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 修改销售易账号
+ */
+export function updateXsyAccount(data) {
+  return request({
+    url: '/xsy/account/update',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 删除销售易账号
+ */
+export function delXsyAccount(id) {
+  return request({
+    url: '/xsy/account/delete/' + id,
+    method: 'post'
+  })
+}
+
+/**
+ * 启用/禁用销售易账号
+ */
+export function updateXsyAccountStatus(id, status) {
+  return request({
+    url: '/xsy/account/status',
+    method: 'post',
+    params: {
+      id: id,
+      status: status
+    }
+  })
+}
+
+/**
+ * 获取授权地址
+ */
+export function getXsyAuthUrl(accountId) {
+  return request({
+    url: '/xiaoShouYi/auth/url/' + accountId,
+    method: 'get'
+  })
+}
+
+
+
+/**
+ * 绑定销售易账号
+ */
+export function bindXsyAccount(data) {
+  return request({
+    url: '/xsy/companyBind/bind',
+    method: 'post',
+    data: data
+  })
+}
+
+export function unbindXsyAccount(data) {
+  return request({
+    url: '/xsy/companyBind/unbind',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 查询绑定关系
+ */
+export function getXsyBind(companyUserId) {
+  return request({
+    url: '/xsy/bind/get',
+    method: 'get',
+    params: {
+      companyUserId: companyUserId
+    }
+  })
+}
+
+
+
+/**
+ * 获取授权URL(新接口)
+ */
+export function getXsyOauthUrl(accountId) {
+  return request({
+    url: '/xiaoShouYi/New/auth/url/' + accountId,
+    method: 'get'
+  })
+}
+
+
+
+/**
+ * 生成素材追踪链接
+ */
+export function generateXsyLink(companyUserId, data) {
+  return request({
+    url: '/xiaoShouYi/New/generateLink',
+    method: 'post',
+    params: {
+      companyUserId: companyUserId
+    },
+    data: data
+  })
+}
+
+/**
+ * 发送确认
+ */
+export function sendXsyConfirm(companyUserId, forwardId, forwardType) {
+  return request({
+    url: '/xiaoShouYi/New/sendConfirm',
+    method: 'post',
+    params: {
+      companyUserId: companyUserId,
+      forwardId: forwardId,
+      forwardType: forwardType
+    }
+  })
+}
+
+/**
+ * 查询素材列表
+ */
+export function queryXsyMaterials(companyUserId, data) {
+  return request({
+    url: '/xiaoShouYi/New/materials/query',
+    method: 'post',
+    params: {
+      companyUserId: companyUserId
+    },
+    data: data
+  })
+}
+
+/**
+ * 上传素材文件
+ */
+export function uploadXsyFile(companyUserId, file, isVideo) {
+  const formData = new FormData()
+  formData.append('file', file)
+  formData.append('isVideo', isVideo == null ? false : isVideo)
+
+  return request({
+    url: '/xiaoShouYi/New/uploadFile?companyUserId=' + companyUserId,
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+/**
+ * 创建素材
+ */
+export function createXsyMaterial(companyUserId, data) {
+  return request({
+    url: '/xiaoShouYi/New/createMaterial',
+    method: 'post',
+    params: {
+      companyUserId: companyUserId
+    },
+    data: data
+  })
+}
+
+/**
+ * 上传并创建素材
+ */
+export function createXsyMaterialWithUpload(params) {
+  const formData = new FormData()
+  formData.append('companyUserId', params.companyUserId)
+  formData.append('file', params.file)
+  formData.append('isVideo', params.isVideo == null ? false : params.isVideo)
+  formData.append('corpName', params.corpName)
+  formData.append('materialType', params.materialType)
+  formData.append('categoryName', params.categoryName)
+  formData.append('title', params.title)
+
+  return request({
+    url: '/xiaoShouYi/New/createMaterialWithUpload',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+/**
+ * 完整流程:生成链接并发送确认
+ */
+export function fullProcessXsy(companyUserId, data) {
+  return request({
+    url: '/xiaoShouYi/New/fullProcess',
+    method: 'post',
+    params: {
+      companyUserId: companyUserId
+    },
+    data: data
+  })
+}
+

BIN
src/assets/logo/tbyg.png


+ 134 - 0
src/views/his/company/index.vue

@@ -219,6 +219,25 @@
             @click="handleRevenue(scope.row)"
             v-hasPermi="['company:company:revenue']"
           >分账配置</el-button>
+
+          <el-button
+            v-if="!scope.row.accountId"
+            size="mini"
+            type="text"
+            icon="el-icon-link"
+            @click="handleBindXsy(scope.row)"
+          >绑定销售易
+          </el-button>
+
+          <el-button
+            v-if="scope.row.accountId"
+            size="mini"
+            type="text"
+            icon="el-icon-close"
+            @click="handleUnbindXsy(scope.row)"
+          >解绑销售易
+          </el-button>
+
         </template>
       </el-table-column>
     </el-table>
@@ -735,6 +754,38 @@
       </div>
     </el-dialog>
 
+    <!-- 绑定销售易账号 -->
+    <!-- 绑定销售易账号 -->
+    <el-dialog :title="xsyBindDialog.title" :visible.sync="xsyBindDialog.open" width="500px" append-to-body>
+      <el-form ref="xsyBindFormRef" :model="xsyBindForm" label-width="100px">
+        <el-form-item label="公司名称">
+          <el-input v-model="xsyBindForm.companyName" disabled />
+        </el-form-item>
+        <el-form-item label="销售易账号" prop="accountId">
+          <el-select
+            v-model="xsyBindForm.accountId"
+            filterable
+            clearable
+            placeholder="请选择销售易账号"
+            style="width: 100%;"
+            :loading="xsyBindDialog.loading"
+          >
+            <el-option
+              v-for="item in xsyAccountList"
+              :key="item.id"
+              :label="item.accountName"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitBindXsy">确 定</el-button>
+        <el-button @click="cancelBindXsy">取 消</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -763,6 +814,7 @@ import { getConfigByKey } from '@/api/system/config'
 import { listDept } from '@/api/system/dept'
 import { listAll } from '@/api/course/coursePlaySourceConfig'
 import{ getSignProjectName } from '@/api/course/qw/courseWatchLog'
+import { listXsyAccount, bindXsyAccount, unbindXsyAccount } from '@/api/xsy/xsy'
 export default {
   name: 'Company',
   data() {
@@ -948,6 +1000,20 @@ export default {
         open: false,
         title: '批量修改小程序'
       },
+      // 销售易绑定弹窗
+      xsyBindDialog: {
+        open: false,
+        title: '绑定销售易账号',
+        loading: false
+      },
+      // 销售易账号列表
+      xsyAccountList: [],
+      // 绑定表单
+      xsyBindForm: {
+        companyId: null,
+        companyName: '',
+        accountId: null
+      },
 
     }
   },
@@ -1400,6 +1466,74 @@ export default {
         }
       });
     },
+    // 打开绑定销售易账号弹窗
+    handleBindXsy(row) {
+      this.xsyBindForm = {
+        companyId: row.companyId,
+        companyName: row.companyName,
+        accountId: null
+      }
+      this.xsyAccountList = []
+      this.xsyBindDialog.open = true
+      this.xsyBindDialog.loading = true
+
+      listXsyAccount({
+        pageNum: 1,
+        pageSize: 9999
+      }).then(res => {
+        this.xsyAccountList = res.rows || []
+      }).finally(() => {
+        this.xsyBindDialog.loading = false
+      })
+    },
+
+    // 取消绑定
+    cancelBindXsy() {
+      this.xsyBindDialog.open = false
+      this.xsyBindForm = {
+        companyId: null,
+        companyName: '',
+        accountId: null
+      }
+    },
+
+    // 提交绑定
+    submitBindXsy() {
+      if (!this.xsyBindForm.accountId) {
+        this.$message.warning('请选择销售易账号')
+        return
+      }
+
+      bindXsyAccount({
+        companyId: this.xsyBindForm.companyId,
+        accountId: this.xsyBindForm.accountId
+      }).then(res => {
+        if (res.code === 200) {
+          this.msgSuccess('绑定成功')
+          this.xsyBindDialog.open = false
+          this.getList()
+        }
+      })
+    },
+
+    // 解绑销售易账号
+    handleUnbindXsy(row) {
+      this.$confirm('是否确认解绑当前销售易账号?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return unbindXsyAccount({
+          companyId: row.companyId,
+          accountId: row.accountId
+        })
+      }).then(res => {
+        if (res.code === 200) {
+          this.msgSuccess('解绑成功')
+          this.getList()
+        }
+      }).catch(() => {})
+    },
   }
 }
 </script>

+ 538 - 1
src/views/hisStore/storeProduct/index.vue

@@ -7,7 +7,7 @@
       </el-form-item>
 
       <el-form-item label="商品名称" prop="productName">
-        <el-input
+        <el-inputa
           v-model="queryParams.productName"
           placeholder="请输入商品名称"
           clearable
@@ -329,6 +329,13 @@
       </el-table-column>
       <el-table-column label="销量" align="center" prop="sales" />
       <el-table-column label="库存" align="center" prop="stock" />
+      <el-table-column label="活动类型" align="center" prop="activityType" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.activityType === 6" type="danger" size="small">秒杀</el-tag>
+          <el-tag v-else-if="scope.row.activityType === 7" type="warning" size="small">限时折扣</el-tag>
+          <el-tag v-else type="info" size="small">无</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="类型" align="center" prop="productType" >
         <template slot-scope="scope">
           <el-tag prop="productType" v-for="(item, index) in productTypeOptions"    v-if="scope.row.productType==item.dictValue">{{item.dictLabel}}</el-tag>
@@ -343,6 +350,14 @@
       </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-s-flag"
+            @click="handleSetActivity(scope.row)"
+            v-hasPermi="['store:storeProduct:edit']"
+            v-if="scope.row.isAudit === '1'"
+          >设置活动</el-button>
           <el-button
             size="mini"
             type="text"
@@ -960,6 +975,131 @@
       </div>
     </el-dialog>
 
+    <!-- 设置活动弹窗 -->
+    <el-dialog title="设置活动" :visible.sync="activityDialogVisible" width="780px" append-to-body :close-on-click-modal="false">
+      <!-- 活动进行中提示 -->
+      <el-alert
+        v-if="activityIsOngoing"
+        :title="'当前商品正在' + (activityOngoingType === 6 ? '秒杀' : '限时折扣') + '活动中,活动进行期间无法修改活动设置'"
+        type="error"
+        :closable="false"
+        show-icon
+        style="margin-bottom:15px;"
+      />
+
+      <el-form ref="activityForm" :model="activityForm" :rules="activityRules" label-width="100px" :disabled="activityIsOngoing">
+        <!-- 活动类型 -->
+        <el-form-item label="活动类型" prop="activityType">
+          <el-radio-group v-model="activityForm.activityType">
+            <el-radio :label="0">不参与活动</el-radio>
+            <el-radio :label="6">
+              <i class="el-icon-lightning" style="color:#F56C6C;margin-right:2px;"></i>秒杀
+            </el-radio>
+            <el-radio :label="7">
+              <i class="el-icon-time" style="color:#E6A23C;margin-right:2px;"></i>限时折扣
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <!-- 统一活动时间(所有规格共用) -->
+        <template v-if="activityForm.activityType === 6 || activityForm.activityType === 7">
+          <el-form-item label="活动时间" prop="startTime">
+            <el-date-picker v-model="activityForm.startTime" type="datetime" placeholder="开始时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:200px" size="small"/>
+            <span style="margin:0 8px;color:#909399;">至</span>
+            <el-date-picker v-model="activityForm.endTime" type="datetime" placeholder="结束时间" value-format="yyyy-MM-dd HH:mm:ss" style="width:200px" size="small"/>
+          </el-form-item>
+        </template>
+
+        <template v-if="activityForm.activityType === 6 || activityForm.activityType === 7">
+          <!-- 选择参与规格 -->
+          <el-divider content-position="left">
+            <span style="font-size:14px;font-weight:500;">选择参与规格</span>
+          </el-divider>
+          <div class="spec-select-list">
+            <div
+              v-for="(spec, idx) in activityAllSpecs"
+              :key="idx"
+              :class="['spec-select-item', spec.selected ? 'spec-select-active' : '']"
+              @click="toggleSpecSelect(spec)"
+            >
+              <el-popover placement="right" trigger="hover" v-if="spec.image">
+                <img slot="reference" :src="spec.image" class="spec-thumb" />
+                <img :src="spec.image" style="max-width:200px;max-height:200px;" />
+              </el-popover>
+              <div class="spec-select-info">
+                <span class="spec-select-name">{{ spec.specName }}</span>
+                <span class="spec-select-detail">¥{{ spec.price }} · 库存{{ spec.specStock }}</span>
+              </div>
+              <i :class="spec.selected ? 'el-icon-circle-check' : 'el-icon-circle-plus-outline'" :style="{color: spec.selected ? '#409EFF' : '#C0C4CC', fontSize: '20px'}"></i>
+            </div>
+          </div>
+
+          <!-- 已选规格活动设置 -->
+          <template v-if="selectedSpecList.length > 0">
+            <el-divider content-position="left">
+              <span style="font-size:14px;font-weight:500;">规格活动设置</span>
+              <span style="font-size:12px;color:#909399;margin-left:8px;">已选 {{ selectedSpecList.length }} 项</span>
+            </el-divider>
+            <div class="activity-spec-list">
+              <div v-for="(spec, idx) in selectedSpecList" :key="idx" class="activity-spec-card">
+                <div class="spec-card-header">
+                  <div class="spec-header-left">
+                    <el-popover placement="right" trigger="hover" v-if="spec.image">
+                      <img slot="reference" :src="spec.image" class="spec-thumb" />
+                      <img :src="spec.image" style="max-width:200px;max-height:200px;" />
+                    </el-popover>
+                    <span class="spec-name">{{ spec.specName }}</span>
+                    <i class="el-icon-remove-outline" style="color:#F56C6C;font-size:16px;cursor:pointer;margin-left:8px;" title="移除" @click="toggleSpecSelect(spec)"></i>
+                  </div>
+                </div>
+                <div class="spec-detail-row">
+                  <span class="spec-detail-item">售价 <b>¥{{ spec.price }}</b></span>
+                  <span class="spec-detail-item">代理价 <b>¥{{ spec.agentPrice }}</b></span>
+                  <span class="spec-detail-item">成本价 <b>¥{{ spec.cost }}</b></span>
+                  <span class="spec-detail-item">原价 <b>¥{{ spec.otPrice }}</b></span>
+                  <span class="spec-detail-item">库存 <b>{{ spec.specStock }}</b></span>
+                  <span class="spec-detail-item" v-if="spec.barCode">编号 <b>{{ spec.barCode }}</b></span>
+                </div>
+                <el-row :gutter="16" class="spec-card-body">
+                  <!-- 秒杀价 -->
+                  <el-col :span="12" v-if="activityForm.activityType === 6">
+                    <div class="spec-field">
+                      <label>秒杀价</label>
+                      <el-input-number v-model="spec.flashPrice" :precision="2" :min="0" :max="spec.originalPrice" size="small" placeholder="秒杀价" style="width:100%" controls-position="right"/>
+                    </div>
+                  </el-col>
+                  <!-- 折扣设置 -->
+                  <el-col :span="8" v-if="activityForm.activityType === 7">
+                    <div class="spec-field">
+                      <label>折扣 <span style="color:#E6A23C;font-weight:500;">{{ spec.discountValue ? spec.discountValue.toFixed(1) : '10.0' }}折</span></label>
+                      <el-slider v-model="spec.discountValue" :min="1" :max="10" :step="0.1" :show-tooltip="false" @change="handleSpecDiscountChange(spec)"/>
+                    </div>
+                  </el-col>
+                  <el-col :span="8" v-if="activityForm.activityType === 7">
+                    <div class="spec-field">
+                      <label>折扣价</label>
+                      <el-input-number v-model="spec.discountPrice" :precision="2" :min="0" size="small" placeholder="折扣价" style="width:100%" controls-position="right" @change="handleSpecDiscountPriceChange(spec)"/>
+                    </div>
+                  </el-col>
+                </el-row>
+              </div>
+            </div>
+          </template>
+          <div v-else style="text-align:center;padding:20px 0;color:#C0C4CC;font-size:13px;">
+            请在上方选择需要参与活动的规格
+          </div>
+        </template>
+
+        <el-form-item label="备注" style="margin-top:15px;">
+          <el-input v-model="activityForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer" v-if="!activityIsOngoing">
+        <el-button @click="activityDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitActivityForm" :loading="activitySubmitLoading">确 定</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -986,6 +1126,8 @@ import singleImg from '@/components/Material/single'
 import { getCompanyList } from "@/api/company/company";
 import { listStore } from '@/api/hisStore/store'
 import {list as getAppMallOptions} from "@/api/course/coursePlaySourceConfig";
+import { getActivityByProductId, saveActivity } from "@/api/hisStore/productActivity";
+import { listStoreProductAttrValue } from "@/api/hisStore/storeProductAttrValue";
 export default {
   name: "HisStoreProduct",
   components: {
@@ -1005,6 +1147,11 @@ export default {
       this.form.drugImage = val.join(',');
     }
   },
+  computed: {
+    selectedSpecList() {
+      return this.activityAllSpecs.filter(s => s.selected);
+    }
+  },
   data() {
     return {
       isMedicalMall: this.$store.state.user.medicalMallConfig.medicalMall,
@@ -1084,6 +1231,24 @@ export default {
       // 企业列表
       companyOptions:[],
       storeOptions:[],
+      // 活动设置相关
+      activityDialogVisible: false,
+      activityIsOngoing: false,
+      activityOngoingType: null,
+      activityCurrentProduct: null,
+      activitySubmitLoading: false,
+      activityForm: {
+        activityType: 0,
+        startTime: null,
+        endTime: null,
+        remark: null
+      },
+      activitySpecList: [],
+      activityAllSpecs: [],
+      activityRules: {
+        activityType: [{ required: true, message: '请选择活动类型', trigger: 'change' }],
+        startTime: [{ required: true, message: '请选择活动开始时间', trigger: 'change' }]
+      },
       // 遮罩层
       loading: true,
       // 选中数组
@@ -1816,6 +1981,246 @@ export default {
         this.msgSuccess("复制成功");
       }).catch(function() {});
     },
+    /** 设置活动按钮操作 */
+    handleSetActivity(row) {
+      // // 校验审核状态:只有审核通过的商品才能设置活动
+      // if (row.isAudit !== 1) {
+      //   this.$message.warning('商品审核通过后才能设置活动');
+      //   return;
+      // }
+      this.activityCurrentProduct = row;
+      this.activityIsOngoing = false;
+      this.activityOngoingType = null;
+      this.activityForm = {
+        activityType: 0,
+        startTime: null,
+        endTime: null,
+        remark: null
+      };
+      this.activitySpecList = [];
+      this.activityAllSpecs = [];
+
+      // 先查询已有活动设置
+      getActivityByProductId(row.productId).then(res => {
+        const activityList = res.data || [];
+        this.activityIsOngoing = res.isOngoing || false;
+        this.activityOngoingType = res.ongoingActivityType || null;
+
+        if (activityList.length > 0) {
+          // 回显已有活动设置
+          const first = activityList[0];
+          this.activityForm.activityType = first.activityType || 0;
+          this.activityForm.remark = first.remark;
+          this.activityForm.startTime = first.startTime || null;
+          this.activityForm.endTime = first.endTime || null;
+        } else {
+          // 从商品表获取activityType
+          this.activityForm.activityType = row.activityType || 0;
+        }
+
+        // 加载商品规格数据
+        this.loadActivitySpecList(row.productId, activityList);
+
+        this.activityDialogVisible = true;
+      }).catch(() => {
+        // 查询失败时仍然打开弹窗,使用商品行数据
+        this.activityForm.activityType = row.activityType || 0;
+        this.loadActivitySpecList(row.productId, []);
+        this.activityDialogVisible = true;
+      });
+    },
+
+    /** 加载活动规格列表 */
+    loadActivitySpecList(productId, existActivityList) {
+      // 先获取商品基本信息(确定specType),再获取规格值列表
+      getStoreProduct(productId).then(res => {
+        const productData = res.data;
+        listStoreProductAttrValue({ productId: productId, pageNum: 1, pageSize: 999 }).then(attrRes => {
+          const attrValues = attrRes.rows || [];
+          let specList = attrValues.map(v => ({
+            specId: v.id,
+            specName: v.sku || ('规格' + v.id),
+            image: v.image || productData.image || null,
+            price: v.price || 0,
+            agentPrice: v.agentPrice || 0,
+            cost: v.cost || 0,
+            otPrice: v.otPrice || 0,
+            specStock: v.stock || 0,
+            barCode: v.barCode || '',
+            originalPrice: v.price || 0,
+            startTime: null,
+            endTime: null,
+            flashPrice: null,
+            discountValue: 10,
+            discount: null,
+            discountPrice: null,
+            stock: null,
+            selected: false
+          }));
+
+          // 回显已有活动数据
+          this.mergeExistActivity(specList, existActivityList);
+          this.activityAllSpecs = specList;
+        })
+      });
+    },
+
+    /** 切换规格选中状态 */
+    toggleSpecSelect(spec) {
+      spec.selected = !spec.selected;
+    },
+
+    /** 合并已有活动数据到规格列表 */
+    mergeExistActivity(specList, existActivityList) {
+      if (existActivityList && existActivityList.length > 0) {
+        specList.forEach(spec => {
+          const matched = existActivityList.find(a => {
+            if (spec.specId !== null) {
+              return a.specId === spec.specId;
+            } else {
+              return true;
+            }
+          });
+          if (matched) {
+            spec.selected = true;
+            spec.flashPrice = matched.flashPrice;
+            spec.discount = matched.discount;
+            spec.discountPrice = matched.discountPrice;
+            spec.stock = matched.stock;
+            spec.originalPrice = matched.originalPrice || spec.originalPrice;
+            spec.startTime = matched.startTime;
+            spec.endTime = matched.endTime;
+            // 折扣值转换:discount存的是0.8格式,滑块需要8格式
+            if (matched.discount) {
+              spec.discountValue = Number((matched.discount * 10).toFixed(1));
+            }
+          }
+        });
+      }
+    },
+
+    /** 折扣滑块变化时计算折扣价 */
+    handleSpecDiscountChange(row) {
+      if (row.originalPrice && row.discountValue) {
+        const discount = row.discountValue / 10;
+        row.discount = discount;
+        row.discountPrice = Math.round(row.originalPrice * discount * 100) / 100;
+      }
+    },
+
+    /** 折扣价变化时反算折扣 */
+    handleSpecDiscountPriceChange(row) {
+      if (row.originalPrice && row.discountPrice) {
+        const discount = row.discountPrice / row.originalPrice;
+        row.discount = Math.round(discount * 1000) / 1000;
+        row.discountValue = Math.round(discount * 100) / 10;
+      }
+    },
+
+    /** 提交活动设置 */
+    submitActivityForm() {
+      this.$refs['activityForm'].validate(valid => {
+        if (!valid) return;
+
+        const { activityType, remark } = this.activityForm;
+
+        if (activityType === 0) {
+          // 不参与活动,直接保存
+          this.activitySubmitLoading = true;
+          saveActivity({
+            productId: this.activityCurrentProduct.productId,
+            activityType: 0,
+            activityList: []
+          }).then(res => {
+            if (res.code === 200) {
+              this.$message.success('活动设置已清除');
+              this.activityDialogVisible = false;
+              this.getList();
+            } else {
+              this.$message.error(res.msg || '保存失败');
+            }
+          }).finally(() => {
+            this.activitySubmitLoading = false;
+          });
+          return;
+        }
+
+        // 校验统一活动时间
+        if (!this.activityForm.startTime) {
+          this.$message.warning('请选择活动开始时间');
+          return;
+        }
+        if (!this.activityForm.endTime) {
+          this.$message.warning('请选择活动结束时间');
+          return;
+        }
+
+        // 校验规格活动数据
+        let hasError = false;
+        const activityList = this.selectedSpecList.map(spec => {
+          const item = {
+            specId: spec.specId,
+            originalPrice: spec.originalPrice,
+            stock: spec.stock,
+            startTime: this.activityForm.startTime,
+            endTime: this.activityForm.endTime
+          };
+          if (activityType === 6) {
+            // 秒杀
+            if (!spec.flashPrice && spec.flashPrice !== 0) {
+              this.$message.warning('请填写规格【' + spec.specName + '】的秒杀价');
+              hasError = true;
+              return null;
+            }
+            item.flashPrice = spec.flashPrice;
+          } else if (activityType === 7) {
+            // 限时折扣
+            if (!spec.discount && spec.discount !== 0) {
+              this.$message.warning('请填写规格【' + spec.specName + '】的折扣');
+              hasError = true;
+              return null;
+            }
+            item.discount = spec.discount;
+            item.discountPrice = spec.discountPrice;
+          }
+
+          // if (!spec.stock && spec.stock !== 0) {
+          //   this.$message.warning('请填写规格【' + spec.specName + '】的活动库存');
+          //   hasError = true;
+          //   return null;
+          // }
+
+          return item;
+        });
+
+        if (hasError) return;
+
+        // 过滤掉null
+        const validList = activityList.filter(item => item !== null);
+        if (validList.length === 0) {
+          this.$message.warning('请至少设置一个规格的活动信息');
+          return;
+        }
+
+        this.activitySubmitLoading = true;
+        saveActivity({
+          productId: this.activityCurrentProduct.productId,
+          activityType: activityType,
+          activityList: validList
+        }).then(res => {
+          if (res.code === 200) {
+            this.$message.success('活动设置保存成功');
+            this.activityDialogVisible = false;
+            this.getList();
+          } else {
+            this.$message.error(res.msg || '保存失败');
+          }
+        }).finally(() => {
+          this.activitySubmitLoading = false;
+        });
+      });
+    },
+
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
@@ -1832,3 +2237,135 @@ export default {
   }
 };
 </script>
+
+<style scoped>
+/* 规格选择列表 */
+.spec-select-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 4px;
+}
+.spec-select-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  border: 1px dashed #DCDFE6;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.2s;
+  background: #fff;
+  min-width: 160px;
+}
+.spec-select-item:hover {
+  border-color: #409EFF;
+  background: #ECF5FF;
+}
+.spec-select-active {
+  border-style: solid;
+  border-color: #409EFF;
+  background: #ECF5FF;
+}
+.spec-select-info {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  flex: 1;
+  min-width: 0;
+}
+.spec-select-name {
+  font-size: 13px;
+  font-weight: 500;
+  color: #303133;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.spec-select-detail {
+  font-size: 11px;
+  color: #909399;
+}
+
+/* 活动规格卡片列表 */
+.activity-spec-list {
+  max-height: 400px;
+  overflow-y: auto;
+  padding-right: 5px;
+}
+.activity-spec-card {
+  border: 1px solid #EBEEF5;
+  border-radius: 6px;
+  margin-bottom: 12px;
+  background: #FAFAFA;
+  transition: all 0.2s;
+}
+.activity-spec-card:hover {
+  border-color: #409EFF;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+}
+.spec-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 16px;
+  border-bottom: 1px solid #EBEEF5;
+  background: #F5F7FA;
+  border-radius: 6px 6px 0 0;
+}
+.spec-card-header .spec-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+}
+.spec-header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.spec-thumb {
+  width: 32px;
+  height: 32px;
+  border-radius: 4px;
+  object-fit: cover;
+  border: 1px solid #EBEEF5;
+  flex-shrink: 0;
+}
+.spec-card-header .spec-info {
+  font-size: 12px;
+  color: #909399;
+}
+.spec-card-header .spec-info b {
+  color: #606266;
+}
+.spec-detail-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px 16px;
+  padding: 8px 16px;
+  background: #F5F7FA;
+  border-bottom: 1px solid #EBEEF5;
+}
+.spec-detail-item {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+}
+.spec-detail-item b {
+  color: #606266;
+  font-weight: 500;
+}
+.spec-card-body {
+  padding: 12px 16px 8px;
+}
+.spec-field {
+  margin-bottom: 4px;
+}
+.spec-field label {
+  display: block;
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 6px;
+  line-height: 1;
+}
+</style>

+ 5 - 0
src/views/system/config/config.vue

@@ -1363,6 +1363,11 @@
               <el-input v-model="form18.realLinkDomainName"></el-input>
             </el-tooltip>
           </el-form-item>
+          <el-form-item label="外网看课域名">
+            <el-tooltip class="item" effect="dark" content="真链域名" placement="top-end">
+              <el-input  v-model="form18.realLinkGjDomainName"     ></el-input>
+            </el-tooltip>
+          </el-form-item>
           <el-form-item label="网页授权域名">
             <el-tooltip class="item" effect="dark" content="公众号网页授权域名" placement="top-end">
               <el-input v-model="form18.authDomainName"></el-input>

+ 245 - 0
src/views/xsy/index.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="app-container">
+
+    <!-- 查询 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
+
+      <el-form-item label="ClientId">
+        <el-input v-model="queryParams.clientId" placeholder="请输入ClientId" size="small"/>
+      </el-form-item>
+
+      <el-form-item label="状态">
+        <el-select v-model="queryParams.status" clearable size="small">
+          <el-option label="启用" :value="1"/>
+          <el-option label="禁用" :value="0"/>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+
+    </el-form>
+
+    <!-- 按钮 -->
+    <el-row class="mb8">
+      <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
+      <el-button type="success" icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
+      <el-button type="danger" icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
+    </el-row>
+
+    <!-- 表格 -->
+    <el-table v-loading="loading" :data="accountList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55"/>
+      <el-table-column prop="id" label="ID"/>
+      <el-table-column prop="accountName" label="账号名称"/>
+      <el-table-column prop="clientId" label="ClientId"/>
+      <el-table-column prop="redirectUri" label="回调地址"/>
+      <el-table-column prop="sendUserId" label="sendUserId"/>
+
+      <el-table-column label="状态">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.status==1?'success':'danger'">
+            {{ scope.row.status==1?'启用':'禁用' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+
+      <el-table-column label="操作">
+        <template slot-scope="scope">
+
+          <el-button size="mini" type="text" @click="handleUpdate(scope.row)">修改</el-button>
+
+          <el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
+
+          <el-button size="mini" type="text" @click="toggleStatus(scope.row)">
+            {{scope.row.status==1?'禁用':'启用'}}
+          </el-button>
+
+          <el-button size="mini" type="text" @click="auth(scope.row.id)">
+            授权
+          </el-button>
+
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页(注意这里 total 用 length) -->
+    <pagination
+      v-show="accountList.length > 0"
+      :total="accountList.length"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+    />
+
+    <!-- 弹窗 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px">
+
+      <el-form ref="form" :model="form">
+
+        <el-form-item label="账号名称">
+          <el-input v-model="form.accountName"/>
+        </el-form-item>
+
+        <el-form-item label="ClientId">
+          <el-input v-model="form.clientId"/>
+        </el-form-item>
+
+        <el-form-item label="ClientSecret">
+          <el-input v-model="form.clientSecret"/>
+        </el-form-item>
+
+        <el-form-item label="回调地址">
+          <el-input v-model="form.redirectUri"/>
+        </el-form-item>
+
+        <el-form-item label="sendUserId">
+          <el-input v-model="form.sendUserId"/>
+        </el-form-item>
+
+      </el-form>
+
+      <div slot="footer">
+        <el-button type="primary" @click="submitForm">确定</el-button>
+        <el-button @click="cancel">取消</el-button>
+      </div>
+
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+  listXsyAccount,
+  addXsyAccount,
+  updateXsyAccount,
+  delXsyAccount,
+  updateXsyAccountStatus,
+  getXsyAuthUrl
+} from "@/api/xsy/xsy";
+
+export default {
+  name: "XsyAccount",
+
+  data() {
+    return {
+      loading: true,
+      accountList: [],
+      ids: [],
+      single: true,
+      multiple: true,
+      showSearch: true,
+
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        clientId: null,
+        status: null
+      },
+
+      form: {},
+      open: false,
+      title: ""
+    };
+  },
+
+  created() {
+    this.getList();
+  },
+
+  methods: {
+
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      listXsyAccount(this.queryParams).then(res => {
+        this.accountList = res.rows || [];
+        this.loading = false;
+      });
+    },
+
+    /** 搜索 */
+    handleQuery() {
+      this.getList();
+    },
+
+    /** 重置 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.getList();
+    },
+
+    /** 选中 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+
+    /** 新增 */
+    handleAdd() {
+      this.form = {};
+      this.open = true;
+      this.title = "新增账号";
+    },
+
+    /** 修改 */
+    handleUpdate(row) {
+      this.form = row || {};
+      this.open = true;
+      this.title = "修改账号";
+    },
+
+    /** 提交 */
+    submitForm() {
+      const api = this.form.id ? updateXsyAccount : addXsyAccount;
+
+      api(this.form).then(res => {
+        if (res.code === 200) {
+          this.msgSuccess("操作成功");
+          this.open = false;
+          this.getList();
+        }
+      });
+    },
+
+    /** 删除 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+
+      this.$confirm('是否确认删除编号为"' + ids + '"的数据项?', "警告")
+        .then(() => {
+          return delXsyAccount(ids);
+        })
+        .then(() => {
+          this.msgSuccess("删除成功");
+          this.getList();
+        });
+    },
+
+    /** 状态切换 */
+    toggleStatus(row) {
+      const status = row.status === 1 ? 0 : 1;
+
+      updateXsyAccountStatus(row.id, status).then(() => {
+        this.msgSuccess("状态修改成功");
+        this.getList();
+      });
+    },
+
+    /** 授权 */
+    auth(id) {
+      getXsyAuthUrl(id).then(res => {
+        window.open(res.msg);
+      });
+    },
+
+    cancel() {
+      this.open = false;
+    }
+
+  }
+};
+</script>