瀏覽代碼

Merge branch 'test'

DESKTOP-SVI9JE1\muzen 2 年之前
父節點
當前提交
7f3364f679

+ 3 - 1
package.json

@@ -50,18 +50,20 @@
     "js-cookie": "3.0.1",
     "jsencrypt": "3.2.1",
     "nprogress": "0.2.0",
-    "quill": "1.3.7",
+    "quill": "^1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
     "vue": "2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
     "vue-meta": "2.4.0",
+    "vue-quill-editor": "^3.0.6",
     "vue-router": "3.4.9",
     "vuedraggable": "^2.24.3",
     "vuex": "3.6.0"
   },
   "devDependencies": {
+    "@types/quill": "^2.0.10",
     "@vue/cli-plugin-babel": "4.4.6",
     "@vue/cli-plugin-eslint": "4.4.6",
     "@vue/cli-service": "4.4.6",

+ 35 - 0
src/api/operation/activity.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/admin/activity/list`,
+    method: 'post',
+    data
+  })
+}
+
+// 详情
+export function detail(id) {
+  return request({
+    url: `/admin/activity/queryById/${id}`,
+    method: 'get'
+  })
+}
+
+// 提交
+export function submit(data) {
+  return request({
+    url: `/admin/activity/addOrUpdate`,
+    method: 'post',
+    data
+  })
+}
+
+// 上下架
+export function change(id, status) {
+  return request({
+    url: `/admin/activity/hitOrSold/${id}/${status}`,
+    method: 'get'
+  })
+}

+ 35 - 0
src/api/operation/agreement.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/admin/protocol/page`,
+    method: 'post',
+    data
+  })
+}
+
+// 新增 编辑
+export function submit(data) {
+  return request({
+    url: `/admin/protocol/addOrUpdate`,
+    method: 'post',
+    data
+  })
+}
+
+// 详情
+export function detail(id) {
+  return request({
+    url: `/admin/protocol/queryById/${id}`,
+    method: 'get'
+  })
+}
+
+// 删除
+export function remove(id) {
+  return request({
+    url: `/admin/protocol/remove/${id}`,
+    method: 'delete'
+  })
+}

+ 10 - 0
src/api/service/recovery.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/admin/recycle/page`,
+    method: 'post',
+    data
+  })
+}

+ 27 - 0
src/api/user/store.js

@@ -0,0 +1,27 @@
+import request from '@/utils/request'
+
+// 列表
+export function list(data) {
+  return request({
+    url: `/admin/appWareHouse/page`,
+    method: 'post',
+    data
+  })
+}
+
+// 新增
+export function submit(data) {
+  return request({
+    url: `/admin/appWareHouse/addOrUpdate`,
+    method: 'post',
+    data
+  })
+}
+
+// 删除
+export function remove(id) {
+  return request({
+    url: `/admin/appWareHouse/remove/${id}`,
+    method: 'delete'
+  })
+}

+ 12 - 12
src/components/Audio/index.vue

@@ -16,19 +16,19 @@ export default {
   },
   methods: {
     getPlayAudio() {
-      if (this.type === 'text') {
-        audio.src = this.src
-        if (!this.src) {
-          this.$message.error('没有找到可播放的资源')
-          return false
-        }
-        this.type = 'delete'
-        this.icon = 'el-icon-video-pause'
-        audio.play()
+      if (!this.src) {
+        this.$message.error('没有找到可播放的资源')
       } else {
-        this.type = 'text'
-        this.icon = 'el-icon-video-play'
-        audio.pause()
+        if (this.type === 'text') {
+          audio.src = this.src
+          this.type = 'delete'
+          this.icon = 'el-icon-video-pause'
+          audio.play()
+        } else {
+          this.type = 'text'
+          this.icon = 'el-icon-video-play'
+          audio.pause()
+        }
       }
     }
   }

+ 256 - 25
src/components/Editor/index.vue

@@ -4,7 +4,75 @@
       :on-error="handleUploadError" :data="data" name="file" :show-file-list="false" :headers="headers"
       style="display: none" ref="upload" v-if="isShow()">
     </el-upload>
+    <el-upload :action="audioUrl" :before-upload="handleBeforeAudio" :on-success="handleAudioSuccess"
+      :on-error="handleAudioError" :data="audioData" :show-file-list="false" :headers="headers" style="display: none"
+      ref="audio" v-if="isShow()" v-loading.fullscreen.lock="fullScreenLoading">
+    </el-upload>
     <div class="editor" ref="editor" :style="styles"></div>
+
+    <!-- 弹窗 -->
+    <el-dialog :visible.sync="dialogVisible" title="选择音频" width="1000px">
+      <el-form inline size="mini">
+        <el-form-item label="音频类型:">
+          <el-select v-model="dialogForm.audioType" placeholder="请选择音频类型">
+            <el-option v-for="item in audioTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="资源平台:">
+          <el-select v-model="dialogForm.platformId" placeholder="请选择资源平台" clearable>
+            <el-option v-for="item in platformOptions" :key="item.value" :value="item.value" :label="item.label" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="付费状态:">
+          <el-select v-model="dialogForm.isFree" placeholder="请选择付费类型" clearable>
+            <el-option v-for="item in freeOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="内容名称:">
+          <el-input v-model="dialogForm.keyword" placeholder="请输入内容名称" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="getSearch">搜索</el-button>
+          <el-button icon="el-icon-refresh" @click="getRefresh">重置</el-button>
+          <el-button type="primary" icon="el-icon-upload2"
+            @click="$refs.audio.$children[0].$refs.input.click();">上传音频</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table :data="tableData" v-loading="loading">
+        <el-table-column label="音频ID" prop="audioId" align="center" show-overflow-tooltip />
+        <el-table-column label="音频名称" prop="audioName" align="center" show-overflow-tooltip />
+        <el-table-column label="音频封面" align="center" width="100px">
+          <template slot-scope="scope">
+            <el-image v-if="scope.row.audioPic" :src="scope.row.audioPic" />
+          </template>
+        </el-table-column>
+        <el-table-column label="音频作者" align="center" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span>
+              {{ scope.row.singerName ? scope.row.singerName : '-' }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="专辑名称" prop="songName" align="center" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span>
+              {{ scope.row.songName ? scope.row.songName : '-' }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="付费类型" prop="isFree" align="center" :formatter="freeFormatter" />
+        <el-table-column label="资源平台" align="center" :formatter="platfromFormatter" />
+        <el-table-column label="操作" align="center">
+          <template slot-scope="scope">
+            <el-button type="text" @click="handleChecked(scope.row)">选择</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div slot="footer">
+        <pagination v-show="total > 0" :total="total" :page.sync="dialogForm.pageNum" :limit.sync="dialogForm.pageSize"
+          @pagination="getList" />
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -14,8 +82,13 @@ import "quill/dist/quill.core.css";
 import "quill/dist/quill.snow.css";
 import "quill/dist/quill.bubble.css";
 import { getToken } from "@/utils/auth";
+import Audio from "@/store/modules/editor";
+Quill.register(Audio, true);
 
+import { list } from '@/api/operation/channel'
+import { platformMixin, isFreeMixin, channelMixin } from '@/mixin/index'
 export default {
+  mixins: [platformMixin, isFreeMixin, channelMixin],
   name: "Editor",
   props: {
     /* 编辑器的内容 */
@@ -51,11 +124,16 @@ export default {
   },
   data() {
     return {
+      // 遮罩层
+      fullScreenLoading: false,
+      loading: false,
       uploadUrl: process.env.VUE_APP_BASE_API + "/system/file/picture/upload", // 上传的图片服务器地址
+      audioUrl: process.env.VUE_APP_BASE_API + "/system/file/mp3/upload", // 上传的音频服务器地址
       headers: {
         Authorization: "Bearer " + getToken()
       },
       data: {},
+      audioData: {},
       Quill: null,
       currentValue: "",
       options: {
@@ -64,22 +142,52 @@ export default {
         debug: "warn",
         modules: {
           // 工具栏配置
-          toolbar: [
-            ["bold", "italic", "underline", "strike"],       // 加粗 斜体 下划线 删除线
-            ["blockquote", "code-block"],                    // 引用  代码块
-            [{ list: "ordered" }, { list: "bullet" }],       // 有序、无序列表
-            [{ indent: "-1" }, { indent: "+1" }],            // 缩进
-            [{ size: ["small", false, "large", "huge"] }],   // 字体大小
-            [{ header: [1, 2, 3, 4, 5, 6, false] }],         // 标题
-            [{ color: [] }, { background: [] }],             // 字体颜色、字体背景颜色
-            [{ align: [] }],                                 // 对齐方式
-            ["clean"],                                       // 清除文本格式
-            ["link", "image", "video"]                       // 链接、图片、视频
-          ],
+          toolbar: {
+            container: [
+              ["bold", "italic", "underline", "strike"],       // 加粗 斜体 下划线 删除线
+              ["blockquote", "code-block"],                    // 引用  代码块
+              [{ list: "ordered" }, { list: "bullet" }],       // 有序、无序列表
+              // [{ indent: "-1" }, { indent: "+1" }],            // 缩进
+              [{ size: ["small", false, "large", "huge"] }],   // 字体大小
+              [{ header: [1, 2, 3, 4, 5, 6, false] }],         // 标题
+              [{ color: [] }, { background: [] }],             // 字体颜色、字体背景颜色
+              [{ align: [] }],                                 // 对齐方式
+              ["clean"],                                       // 清除文本格式
+              ["link", "image", "video", "audio"]              // 链接、图片、视频
+            ]
+          }
+        },
+        initAudioButton: function () {
+          const AudioButton = document.querySelector('.ql-audio')
+          AudioButton.classList.add('el-icon-headset')
+          AudioButton.style.cssText = "font-size: 16px; font-weight: bold"
         },
         placeholder: "请输入内容",
         readOnly: this.readOnly,
       },
+
+      // 弹窗
+      dialogVisible: false,
+      dialogForm: {
+        pageNum: 1,
+        pageSize: 10,
+        status: 1,
+        audioType: 2
+      },
+      // 列表
+      tableData: [],
+      total: 0,
+      // 音频类型
+      audioTypeOptions: [{
+        value: 2,
+        label: '广播电台'
+      }, {
+        value: 6,
+        label: '节目'
+      }, {
+        value: 11,
+        label: '歌曲'
+      }]
     };
   },
   computed: {
@@ -105,6 +213,14 @@ export default {
         }
       },
       immediate: true,
+    },
+    'dialogForm.audioType'(val) {
+      if (val) {
+        this.getPlatform({
+          audioType: val
+        })
+        this.getList()
+      }
     }
   },
   mounted() {
@@ -117,18 +233,9 @@ export default {
     init() {
       const editor = this.$refs.editor;
       this.Quill = new Quill(editor, this.options);
-      // 如果设置了上传地址则自定义图片上传事件
-      if (this.type == 'url') {
-        let toolbar = this.Quill.getModule("toolbar");
-        toolbar.addHandler("image", (value) => {
-          this.uploadType = "image";
-          if (value) {
-            this.$refs.upload.$children[0].$refs.input.click();
-          } else {
-            this.quill.format("image", false);
-          }
-        });
-      }
+      this.editorImage()
+      this.editorAudio()
+      this.options.initAudioButton()
       this.Quill.pasteHTML(this.currentValue);
       this.Quill.on("text-change", (delta, oldDelta, source) => {
         const html = this.$refs.editor.children[0].innerHTML;
@@ -148,6 +255,20 @@ export default {
         this.$emit("on-editor-change", eventName, ...args);
       });
     },
+    editorImage() {
+      // 如果设置了上传地址则自定义图片上传事件
+      if (this.type == 'url') {
+        let toolbar = this.Quill.getModule("toolbar");
+        toolbar.addHandler("image", (value) => {
+          this.uploadType = "image";
+          if (value) {
+            this.$refs.upload.$children[0].$refs.input.click();
+          } else {
+            this.quill.format("image", false);
+          }
+        });
+      }
+    },
     handleBeforeUpload(file) {
       this.data.multipartFile = file
     },
@@ -173,7 +294,103 @@ export default {
       if (!this.readOnly) {
         return this.type == 'url'
       }
-    }
+    },
+
+    // 音频
+    editorAudio() {
+      let toolbar = this.Quill.getModule("toolbar");
+      toolbar.addHandler("audio", (value) => {
+        if (value) {
+          this.dialogVisible = true
+          this.getPlatform({
+            audioType: 2
+          })
+          this.getList()
+        } else {
+          this.quill.format("audio", false);
+        }
+      });
+    },
+
+    handleBeforeAudio(file) {
+      this.fullScreenLoading = true
+      this.audioData.multipartFile = file
+    },
+
+    handleAudioSuccess(res) {
+      this.fullScreenLoading = false
+      let quill = this.Quill
+      if (res.code == 0) {
+        let length = quill.getSelection().index;
+        quill.insertEmbed(
+          length + 1,
+          'audio',
+          { src: res.data.url, name: res.data.realName },
+          "api"
+        );
+        quill.insertText(length + 2, "");
+        quill.setSelection(length + 2);
+      } else {
+        this.$message.error(res.message)
+      }
+    },
+
+    handleAudioError() {
+      this.fullScreenLoading = false
+      this.$message.error('插入失败')
+    },
+
+    // 列表
+    getList() {
+      this.loading = true
+      list(this.dialogForm).then(res => {
+        if (res.code === 0) {
+          this.tableData = res.data.records
+          this.total = res.data.total
+          this.loading = false
+        }
+      })
+    },
+
+    // 搜索
+    getSearch() {
+      this.dialogForm.pageNum = 1
+      this.getList()
+    },
+
+    // 重置
+    getRefresh() {
+      this.dialogForm = {
+        pageNum: 1,
+        pageSize: 10,
+        status: 1,
+        audioType: 2
+      }
+      this.getList()
+    },
+
+    // 选择音频
+    handleChecked(row) {
+      let quill = this.Quill
+      let length = quill.getSelection().index;
+      quill.insertEmbed(
+        length + 1,
+        'audio',
+        { src: '', name: row.audioName, poster: row.audioPic, id: row.audioId },
+        "api"
+      );
+      quill.insertText(length + 2, "");
+      quill.setSelection(length + 2);
+      this.$message.success('选择成功!')
+    },
+
+    // 字典翻译
+    freeFormatter(row) {
+      return this.selectDictLabel(this.freeOptions, row.isFree)
+    },
+    platfromFormatter(row) {
+      return this.selectDictLabel(this.platformOptions, row.platformId)
+    },
   },
 };
 </script>
@@ -184,12 +401,15 @@ export default {
   white-space: pre-wrap !important;
   line-height: normal !important;
 }
+
 .quill-img {
   display: none;
 }
+
 .ql-snow .ql-tooltip[data-mode='link']::before {
   content: '请输入链接地址:';
 }
+
 .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
   border-right: 0px;
   content: '保存';
@@ -204,14 +424,17 @@ export default {
 .ql-snow .ql-picker.ql-size .ql-picker-item::before {
   content: '14px';
 }
+
 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
   content: '10px';
 }
+
 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
   content: '18px';
 }
+
 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
   content: '32px';
@@ -221,26 +444,32 @@ export default {
 .ql-snow .ql-picker.ql-header .ql-picker-item::before {
   content: '文本';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
   content: '标题1';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
   content: '标题2';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
   content: '标题3';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
   content: '标题4';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
   content: '标题5';
 }
+
 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
   content: '标题6';
@@ -250,10 +479,12 @@ export default {
 .ql-snow .ql-picker.ql-font .ql-picker-item::before {
   content: '标准字体';
 }
+
 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
   content: '衬线字体';
 }
+
 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
   content: '等宽字体';

+ 20 - 10
src/components/Upload/index.vue

@@ -66,6 +66,11 @@ export default {
       type: String,
       default: ''
     },
+    // 文件名
+    name: {
+      type: String,
+      default: 'multipartFile'
+    },
     // 图片尺寸
     width: Number,
     height: Number,
@@ -76,10 +81,11 @@ export default {
     return {
       // 上传类型
       obj: {
-        'text': `${baseUrl}/system/file/file/upload`,
-        'picture-card': `${baseUrl}/system/file/picture/upload`,
-        'audio': `${baseUrl}/system/file/mp3/upload`,
-        'zip': `${baseUrl}/system/file/file/uploadFolderZip`
+        'text': `${baseUrl}/system/file/file/upload`, // 文件上传
+        'picture-card': `${baseUrl}/system/file/picture/upload`, // 图片上传
+        'audio': `${baseUrl}/system/file/mp3/upload`, // 音频上传
+        'zip': `${baseUrl}/system/file/file/uploadFolderZip`, // 压缩包上传
+        'mp3': `${baseUrl}/admin/program/batch/upload` // 歌曲批量上传
       },
       // 上传地址
       action: '#',
@@ -89,8 +95,6 @@ export default {
       },
       // 额外参数
       data: {},
-      // 文件名
-      name: 'multipartFile',
       // 进度条
       percentage: 0,
       title: '',
@@ -107,7 +111,7 @@ export default {
   watch: {
     url(val) {
       this.form.file = val
-      if(!val) {
+      if (!val) {
         this.percentage = 0
       }
     },
@@ -167,8 +171,14 @@ export default {
 
     // 上传成功
     onSuccess(file) {
-      this.form.file = file.data
-      this.$emit('upload', this.form)
+      if (file.code === 0) {
+        this.form.file = file.data
+        this.$emit('upload', this.form)
+        this.title = '上传成功'
+        this.type = 'success'
+      }else{
+        this.onError()
+      }
     },
 
     // 上传失败
@@ -192,7 +202,7 @@ export default {
 
     // 上传按钮
     isBtn() {
-      return ['text', 'audio', 'zip'].includes(this.listType) || (this.listType === 'picture-card' && this.percentage == 0 && !this.form.file)
+      return ['text', 'audio', 'zip', 'mp3'].includes(this.listType) || (this.listType === 'picture-card' && this.percentage == 0 && !this.form.file)
     },
     // 显示进度条
     isPercentage() {

+ 1 - 1
src/mixin/index.js

@@ -23,7 +23,7 @@ const devMixin = {
     }
   },
   mounted() {
-    getClientTypeList().then(res => {
+    getClientTypeList(this.types ? { types: this.types } : {}).then(res => {
       if (res.code === 0) {
         res.data.map(i => {
           this.devOptions.push({

+ 35 - 1
src/router/index.js

@@ -433,7 +433,7 @@ export const dynamicRoutes = [{
     name: 'recommendDetail',
     meta: {
       title: '推荐详情',
-      activeMenu: '/operation/operationRecommend'
+      activeMenu: '/operation/homePage/operationRecommend'
     }
   }]
 },
@@ -505,6 +505,40 @@ export const dynamicRoutes = [{
     }
   }]
 },
+// 协议管理
+{
+  path: '/operation',
+  component: Layout,
+  hidden: true,
+  permissions: ['operation:agreement:list'],
+  name: 'agreement',
+  children: [{
+    path: 'agreement/detail',
+    component: () => import('@/views/operation/agreement/detail'),
+    name: 'agreementDetail',
+    meta: {
+      title: '协议详情',
+      activeMenu: '/operation/agreement'
+    }
+  }]
+},
+// 活动管理
+{ 
+  path: '/operation',
+  component: Layout,
+  hidden: true,
+  permissions: ['operation:activity:list'],
+  name: 'activity',
+  children: [{
+    path: 'activity/detail',
+    component: () => import('@/views/operation/activity/detail'),
+    name: 'activityDetail',
+    meta: {
+      title: '活动详情',
+      activeMenu: '/operation/activity'
+    }
+  }]
+},
 // 商品管理
 // 商品推荐
 {

+ 39 - 0
src/store/modules/editor.js

@@ -0,0 +1,39 @@
+import Quill from 'quill';
+
+const BlockEmbed = Quill.import('blots/block/embed')
+
+class Audio extends BlockEmbed {
+  static create(value) {
+    // console.log(value, 'value')
+    const node = super.create(value);
+    node.setAttribute('src', value.src);
+    node.setAttribute('controls', true);
+    node.setAttribute('name', value.name.replace(' ', ''));
+    node.setAttribute('id', value.id);
+    node.setAttribute('poster', value.poster)
+    return node;
+  }
+  // 添加value获取当前的audio元素。拿到audio元素的属性。
+  static value(domNode) {
+    // console.log(domNode, 'domNode');
+    const value = {
+      src: '',
+      name: '',
+      id: '',
+      controls: true,
+      poster: ''
+    };
+    // 这里要加判断。不然会显示undefined
+    value.src = domNode.getAttribute('src');
+    value.name = domNode.getAttribute('name');
+    value.poster = domNode.getAttribute('poster');
+    value.id = domNode.getAttribute('id');
+    return value;
+  }
+}
+
+Audio.blotName = 'audio'
+Audio.className = 'ql-audio'
+Audio.tagName = 'audio'
+
+export default Audio

+ 22 - 19
src/views/device/yxw/index.vue

@@ -3,18 +3,21 @@
     <!-- 搜索 -->
     <el-form inline size="mini">
       <el-form-item label="设备号:">
-        <el-input v-model="form1.dsn" placeholder="请输入设备号" clearable />
+        <el-input v-model="form.dsn" placeholder="请输入设备号" clearable />
       </el-form-item>
       <el-form-item label="设备型号:">
-        <el-input v-model="form1.clientType" placeholder="请输入设备型号" clearable />
+        <el-input v-model="form.clientType" placeholder="请输入设备型号" clearable />
+      </el-form-item>
+      <el-form-item label="设备标识:">
+        <el-input v-model="form.deviceMac" placeholder="请输入设备标识" clearable />
       </el-form-item>
       <el-form-item label="设备类型:">
-        <el-select v-model="form1.deviceType" placeholder="请选择设备类型" clearable>
+        <el-select v-model="form.deviceType" placeholder="请选择设备类型" clearable>
           <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </el-form-item>
       <el-form-item label="当前状态:">
-        <el-select v-model="form1.status" placeholder="请选择当前状态" clearable>
+        <el-select v-model="form.status" placeholder="请选择当前状态" clearable>
           <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </el-form-item>
@@ -45,13 +48,13 @@
       <el-table-column prop="updateTimeStr" label="激活时间" align="center" />
       <el-table-column prop="statusStr" label="当前状态" align="center" />
     </el-table>
-    <pagination v-show="total > 0" :total="total" :page.sync="form1.pageIndex" :limit.sync="form1.pageSize"
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageIndex" :limit.sync="form.pageSize"
       @pagination="getList" />
     <!-- 弹窗 -->
     <el-dialog title="新增" :visible.sync="dialogVisible" width="500px">
-      <el-form :model="form2" ref="form" label-width="100px">
+      <el-form :model="dialogForm" ref="form" label-width="100px">
         <el-form-item prop="num" label="设备数量:" :rules="[{ required: true, message: '请输入设备数量', trigger: 'blur' }]">
-          <el-input v-model="form2.num" placeholder="请输入设备数量" />
+          <el-input v-model="dialogForm.num" placeholder="请输入设备数量" />
         </el-form-item>
       </el-form>
       <div slot="footer">
@@ -83,7 +86,7 @@ export default {
         },
       ],
       // 表单
-      form1: {
+      form: {
         pageIndex: 1,
         pageSize: 10,
       },
@@ -98,19 +101,19 @@ export default {
       // 弹窗
       dialogVisible: false,
       // 新增设备数量
-      form2: {
+      dialogForm: {
         num: ""
       }
     };
   },
   watch: {
     createTime(val) {
-      this.form1.startCreateTime = val[0]
-      this.form1.endCreateTime = val[1]
+      this.form.startCreateTime = val[0]
+      this.form.endCreateTime = val[1]
     },
     updateTime(val) {
-      this.form1.startUpdateTime = val[0]
-      this.form1.endUpdateTime = val[1]
+      this.form.startUpdateTime = val[0]
+      this.form.endUpdateTime = val[1]
     },
   },
   mounted() {
@@ -119,13 +122,13 @@ export default {
   methods: {
     // 搜索
     getSearch() {
-      this.form1.pageIndex = 1;
+      this.form.pageIndex = 1;
       this.getList();
     },
 
     // 重置
     getRefresh() {
-      this.form1 = {
+      this.form = {
         pageIndex: 1,
         pageSize: 10,
       };
@@ -136,7 +139,7 @@ export default {
     // 分页
     getList() {
       this.loading = true;
-      getYxwPage(this.form1).then((res) => {
+      getYxwPage(this.form).then((res) => {
         if (res.code === 0) {
           this.tableData = res.data.records;
           this.total = res.data.total;
@@ -149,7 +152,7 @@ export default {
     getSubmit() {
       this.$refs.form.validate((valid) => {
         if (valid) {
-          getYxwAdd(this.form2).then((res) => {
+          getYxwAdd(this.dialogForm).then((res) => {
             if (res.code === 0) {
               this.$message.success("新增成功!");
               this.dialogVisible = false;
@@ -167,7 +170,7 @@ export default {
     getDownLoadNo() {
       this.download(
         `/device/yunxiaowei/exportDeviceNo`,
-        this.form1,
+        this.form,
         `云小微设备号.xlsx`
       );
     },
@@ -176,7 +179,7 @@ export default {
     getDownLoadData() {
       this.download(
         `/device/yunxiaowei/exportDeviceData`,
-        this.form1,
+        this.form,
         `云小微设备数据.xlsx`
       );
     }

+ 5 - 3
src/views/music/album/detail.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
+    <el-form class="form" :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
       <el-form-item label="专辑名称:" prop="name">
         <el-input v-model="form.name" placeholder="请输入专辑名称" show-word-limit />
       </el-form-item>
@@ -317,7 +317,9 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.el-form-item {
-  width: 500px;
+.form {
+  .el-form-item {
+    width: 500px;
+  }
 }
 </style>

+ 7 - 4
src/views/music/blog/detail.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
+    <el-form class="form" :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
       <el-form-item label="播客名称:" prop="name">
         <el-input v-model="form.name" placeholder="请输入播客名称" show-word-limit />
       </el-form-item>
@@ -63,7 +63,7 @@
     </div>
 
     <!-- 弹窗 -->
-    <el-dialog :visible.sync="dialogVisible" title="关联节目" width="1100px">
+    <el-dialog :visible.sync="dialogVisible" title="关联节目" width="1200px">
       <el-form inline size="mini" style="width: 100%">
         <el-form-item label="节目ID:">
           <el-input v-model="dialogForm.id" placeholder="请输入节目ID" clearable />
@@ -374,7 +374,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.el-form-item {
-  width: 500px;
+
+.form {
+  .el-form-item {
+    width: 500px;
+  }
 }
 </style>

+ 1 - 1
src/views/music/list/index.vue

@@ -27,7 +27,7 @@
           v-hasPermi="['music:list:up']">批量上架</el-button>
         <el-button type="primary" :disabled="obj.id === ''" @click="getChange(obj, 2)"
           v-hasPermi="['music:list:down']">批量下架</el-button>
-        <Upload style="margin-left: 10px;" @loading="loading = true" @upload="loading = false" />
+        <Upload listType="mp3" multiple name="multipartFiles" @upload="getList()" style="margin-left: 10px;">批量上传</Upload>
       </el-form-item>
     </el-form>
     <!-- 列表 -->

+ 6 - 3
src/views/music/menu/detail.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
+    <el-form class="form" :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
       <el-form-item label="歌单名称:" prop="name">
         <el-input v-model="form.name" placeholder="请输入歌单名称" show-word-limit />
       </el-form-item>
@@ -276,7 +276,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.el-form-item {
-  width: 500px;
+
+.form {
+  .el-form-item {
+    width: 500px;
+  }
 }
 </style>

+ 12 - 6
src/views/music/radio/detail.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="app-container">
-    <el-form :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
+    <el-form :model="form" ref="form" :rules="rules" label-width="110px" :disabled="disabled">
       <el-form-item label="电台名称:" prop="name">
         <el-input v-model="form.name" placeholder="请输入电台名称" show-word-limit />
       </el-form-item>
       <el-form-item label="资源平台:" prop="platformId">
         <el-select v-model="form.platformId" placeholder="请选择资源平台" :disabled="disabledPlatformId(form.platformId)">
-          <el-option v-for="item in platformOptions" :key="item.value" :value="item.value"
-            :label="item.label" :disabled="disabledJoinType(item.joinType)" />
+          <el-option v-for="item in platformOptions" :key="item.value" :value="item.value" :label="item.label"
+            :disabled="disabledJoinType(item.joinType)" />
         </el-select>
       </el-form-item>
       <el-form-item v-if="form.platformId !== 4" label="地域分类:" prop="addressClassifyId">
@@ -26,14 +26,17 @@
         <el-input v-model="form.description" type="textarea" rows="5" maxlength="300" show-word-limit
           placeholder="请输入电台简介" />
       </el-form-item>
-      <el-form-item label="电台链接:" prop="fullUrl">
-        <el-input v-model="form.fullUrl" placeholder="请输入电台链接" />
+      <el-form-item label="非加速链接:" prop="noStreamUrl">
+        <el-input v-model="form.noStreamUrl" placeholder="请输入非加速链接" />
+      </el-form-item>
+      <el-form-item label="加速链接:">
+        <el-input v-model="form.fullUrl" placeholder="请输入加速链接" />
       </el-form-item>
       <el-form-item label="电台封面:" prop="thumb">
         <Upload listType="picture-card" :url="form.thumb" @upload="upload" :disabled="disabled" />
       </el-form-item>
     </el-form>
-    <div class="form-btn">
+    <div style="margin-left: 110px">
       <el-button @click="cancel">取消</el-button>
       <el-button v-if="!disabled" type="primary" @click="getSubmit">确定</el-button>
     </div>
@@ -68,6 +71,9 @@ export default {
         platformId: [{
           required: true, message: '请选择资源平台', trigger: 'change'
         }],
+        noStreamUrl: [{
+          required: true, message: '请输入非加速链接', trigger: 'blur'
+        }],
         thumb: [{
           required: true, message: '请上传电台封面', trigger: 'change'
         }]

+ 13 - 14
src/views/music/radio/index.vue

@@ -2,13 +2,12 @@
   <div class="app-container">
     <!-- 搜索 -->
     <el-form inline size="mini">
-      <el-form-item label="电台名称:"> 
+      <el-form-item label="电台名称:">
         <el-input v-model="form.name" placeholder="请输入电台名称" clearable />
       </el-form-item>
       <el-form-item label="资源平台:">
         <el-select v-model="form.platformId" placeholder="请选择资源平台" clearable>
-          <el-option v-for="item in platformOptions" :key="item.value" :value="item.value"
-            :label="item.label" />
+          <el-option v-for="item in platformOptions" :key="item.value" :value="item.value" :label="item.label" />
         </el-select>
       </el-form-item>
       <el-form-item label="地域分类:">
@@ -23,16 +22,18 @@
       </el-form-item>
       <el-form-item label="当前状态:">
         <el-select v-model="form.status" placeholder="请选择当前状态" clearable>
-          <el-option v-for="item in onOrOffOptions" :key="item.value" :value="item.value"
-            :label="item.label" />
+          <el-option v-for="item in onOrOffOptions" :key="item.value" :value="item.value" :label="item.label" />
         </el-select>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" @click="getSearch">搜索</el-button>
         <el-button icon="el-icon-refresh" @click="getRefresh">重置</el-button>
-        <el-button type="primary" plain icon="el-icon-plus" @click="getDetail()" v-hasPermi="['music:radio:add']">新增</el-button>
-        <el-button type="primary" :disabled="obj.id === ''" @click="getChange(obj, 1)" v-hasPermi="['music:radio:up']">批量上架</el-button>
-        <el-button type="primary" :disabled="obj.id === ''" @click="getChange(obj, 2)" v-hasPermi="['music:radio:down']">批量下架</el-button>
+        <el-button type="primary" plain icon="el-icon-plus" @click="getDetail()"
+          v-hasPermi="['music:radio:add']">新增</el-button>
+        <el-button type="primary" :disabled="obj.id === ''" @click="getChange(obj, 1)"
+          v-hasPermi="['music:radio:up']">批量上架</el-button>
+        <el-button type="primary" :disabled="obj.id === ''" @click="getChange(obj, 2)"
+          v-hasPermi="['music:radio:down']">批量下架</el-button>
       </el-form-item>
     </el-form>
     <!-- 列表 -->
@@ -53,7 +54,8 @@
         <template slot-scope="scope">
           <el-button type="text" @click="getDetail(scope.row.id, true)">查看</el-button>
           <span v-if="scope.row.status === 2">
-            <el-button type="text" @click="getDetail(scope.row.id)" v-hasPermi="['music:radio:edit']" style="margin-left: 10px">编辑</el-button>
+            <el-button type="text" @click="getDetail(scope.row.id)" v-hasPermi="['music:radio:edit']"
+              style="margin-left: 10px">编辑</el-button>
             <el-button type="text" @click="getChange(scope.row, 1)" v-hasPermi="['music:radio:up']">上架</el-button>
             <el-button type="delete" @click="getDelete(scope.row)" v-hasPermi="['music:radio:delete']">删除</el-button>
           </span>
@@ -62,7 +64,7 @@
         </template>
       </el-table-column>
     </el-table>
-    <pagination v-show="total>0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
       @pagination="getList" />
   </div>
 </template>
@@ -200,7 +202,4 @@ export default {
     }
   },
 }
-</script>
-
-<style lang="scss" scoped>
-</style>
+</script>

+ 194 - 0
src/views/operation/activity/detail.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class='app-container'>
+    <el-form :model="form" :rules="rules" ref="form" label-width="100px" :disabled="disabled">
+      <el-form-item label="活动名称:">
+        <el-input v-model="form.name" placeholder="请输入活动名称" />
+      </el-form-item>
+      <el-form-item label="活动图片:" prop="pic">
+        <Upload listType="picture-card" :url="form.pic" @upload="upload" />
+      </el-form-item>
+      <el-form-item label="有效时间:" prop="timeList">
+        <el-date-picker type="datetimerange" v-model="form.timeList" start-placeholder="开始日期" end-placeholder="结束日期"
+          value-format="yyyy-MM-dd HH:mm:ss" />
+      </el-form-item>
+      <el-form-item label="活动设备:" style="width: 1000px" prop="deviceRespList">
+        <el-button type="primary" icon="el-icon-plus" @click="handlePush" :disabled="disabledActivity">添加</el-button>
+        <el-table :data="form.deviceRespList">
+          <el-table-column label="关联设备" align="center" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <el-form-item :prop="`deviceRespList.${scope.$index}.deviceId`"
+                :rules="{ required: true, trigger: 'change' }">
+                <span v-if="disabledActivity">{{ devOptions.find(i => i.value == scope.row.deviceId).label }}</span>
+                <el-select v-else v-model="scope.row.deviceId" filterable placeholder="请选择关联设备" :disabled="disabledActivity">
+                  <el-option v-for="item in devOptions" :key="item.value" :value="item.value" :label="item.label"
+                    :disabled="item.disabled" />
+                </el-select>
+              </el-form-item>
+            </template>
+          </el-table-column>
+          <el-table-column label="活动权益" align="center">
+            <template slot-scope="scope">
+              <el-form-item :prop="`deviceRespList.${scope.$index}.indate`"
+                :rules="{ required: true, trigger: 'change' }">
+                <el-select v-model="scope.row.indate" placeholder="请选择活动权益" :disabled="disabledActivity">
+                  <el-option v-for="item in serviceTimeOptions" :key="item.value" :value="item.value"
+                    :label="item.label" />
+                </el-select>
+              </el-form-item>
+            </template>
+          </el-table-column>
+          <el-table-column label="领取数量" align="center">
+            <template slot-scope="scope">
+              <el-form-item :prop="`deviceRespList.${scope.$index}.totalNum`"
+                :rules="[{ type: 'number', required: true, min: disabledActivity && num[scope.$index] ? num[scope.$index] : 1, trigger: 'blur' }]">
+                <el-input-number v-model="scope.row.totalNum" :controls="false" :min="1" placeholder="请输入领取数量"
+                  @change="handleChange($event, scope.$index)" />
+              </el-form-item>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center">
+            <template slot-scope="scope">
+              <el-button type="delete" @click="getDelete(scope.row.deviceId, scope.$index)"
+                :disabled="disabledActivity">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-form-item>
+    </el-form>
+    <div class="form-btn">
+      <el-button @click="cancel">取消</el-button>
+      <el-button v-if="!disabled" type="primary" @click="getSubmit">确定</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { detail, submit } from "@/api/operation/activity"
+import { serviceTimeMixin, devMixin } from '@/mixin/index'
+export default {
+  mixins: [serviceTimeMixin, devMixin],
+  data() {
+    return {
+      // 表单
+      form: {
+        deviceRespList: []
+      },
+      // 除蓝牙设备
+      types: [2, 4],
+      // 生效期内禁止修改活动设备
+      disabledActivity: this.$route.query.activityState == 0 ? true : false,
+      // 生效期内领取数量禁止下调
+      num: [],
+      // 只读
+      disabled: Boolean(this.$route.query.boolean),
+      // 校验
+      rules: {
+        pic: [{
+          required: true, message: '请上传活动图片', trigger: 'change'
+        }],
+        timeList: [{
+          required: true, message: '请选择有效时间', trigger: 'change'
+        }],
+        deviceRespList: [{
+          type: 'array', required: true, message: '请添加活动设备', trigger: 'change'
+        }]
+      }
+    }
+  },
+  watch: {
+    'form.deviceRespList': {
+      handler(val) {
+        for (let i in this.devOptions) {
+          this.devOptions[i].disabled = false
+        }
+        // 禁用已关联设备
+        val.map((i, index) => {
+          if (i.deviceId) {
+            let index = this.devOptions.findIndex(j => j.value === i.deviceId)
+            this.devOptions[index].disabled = true
+          }
+        })
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    if (this.$route.query.id) {
+      this.getDetail()
+    }
+  },
+  methods: {
+    // 详情
+    getDetail() {
+      detail(this.$route.query.id).then(res => {
+        if (res.code === 0) {
+          this.form = res.data
+          res.data.deviceRespList.map(i => {
+            this.num.push(i.totalNum)
+          })
+        }
+      })
+    },
+
+    // 上传图片
+    upload(e) {
+      this.form.pic = e.file
+    },
+
+    // 添加
+    handlePush() {
+      this.form.deviceRespList.push({})
+    },
+
+    // 已生效的活动 设备数量不能修改的比之前小
+    handleChange(e, index) {
+      if (this.disabledActivity && this.num[index] > e) {
+        this.$message.error('已生效活动,当前领取数量不可减少')
+      }
+    },
+
+    // 删除
+    getDelete(deviceId, index) {
+      this.form.deviceRespList.splice(index, 1)
+      this.devOptions.map(i => {
+        if (i.value === deviceId) {
+          i.disabled = false
+        }
+      })
+    },
+
+    // 取消
+    cancel() {
+      this.$tab.closeOpenPage('/operation/activity')
+    },
+
+    // 确定
+    getSubmit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          submit(this.form).then(res => {
+            if (res.code === 0) {
+              this.$message.success('提交成功!')
+              this.cancel()
+            }
+          })
+        } else {
+          return false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-form-item {
+  width: 500px;
+}
+
+.el-table__row {
+  .el-form-item {
+    width: 100%;
+  }
+}
+</style>

+ 168 - 0
src/views/operation/activity/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class='app-container'>
+    <!-- 搜索 -->
+    <el-form inline size="mini">
+      <el-form-item label="活动名称:">
+        <el-input v-model="form.name" placeholder="请输入活动名称" clearable />
+      </el-form-item>
+      <el-form-item label="当前状态:">
+        <el-select v-model="form.activityState" placeholder="请选择当前状态" clearable>
+          <el-option v-for="item in currentOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="上下架状态:">
+        <el-select v-model="form.status" placeholder="请选择上下架状态" clearable>
+          <el-option v-for="item in disabledOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="getSearch">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="getRefresh">重置</el-button>
+        <el-button type="primary" icon="el-icon-plus" plain @click="getDetail"
+          v-hasPermi="['operation:activity:add']">新增</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 列表 -->
+    <el-table :data="tableData" v-loading="loading">
+      <el-table-column label="序号" align="center" type="index" />
+      <el-table-column label="活动id" align="center" prop="id" show-overflow-tooltip />
+      <el-table-column label="活动名称" align="center" prop="name" show-overflow-tooltip />
+      <el-table-column label="活动图片" align="center" width="100px">
+        <template slot-scope="scope">
+          <el-image :src="scope.row.pic" />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="有效时间" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">
+          {{ scope.row.timeList.join(' - ') }}
+        </template>
+      </el-table-column>
+      <el-table-column label="当前状态" align="center" :formatter="activityFormatter" width="150px" />
+      <el-table-column label="上下架状态" align="center" :formatter="statusFormatter" width="150px" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" @click="getDetail(scope.row, true)">查看</el-button>
+          <el-button type="text" v-if="scope.row.status === 0" @click="getChange(scope.row.id, 1, '下架')"
+            v-hasPermi="['operation:activity:down']">下架</el-button>
+          <span v-else style="margin: 0 10px">
+            <el-button v-if="scope.row.activityState !== 2" type="text" @click="getDetail(scope.row)"
+              v-hasPermi="['operation:activity:edit']">编辑</el-button>
+            <el-button v-if="scope.row.activityState !== 2" type="text" @click="getChange(scope.row.id, 0, '上架')"
+              v-hasPermi="['operation:activity:up']">上架</el-button>
+            <el-button type="delete" @click="getDelete(scope.row)"
+              v-hasPermi="['operation:activity:delete']">删除</el-button>
+          </span>
+          <el-button type="text" v-clipboard:copy="scope.row.copyUrl" v-clipboard:success="getCopy"
+            v-hasPermi="['operation:activity:copy']">复制链接</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+      @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { list, change } from '@/api/operation/activity'
+import { currentMixin, disabledMixin } from '@/mixin/index'
+export default {
+  mixins: [currentMixin, disabledMixin],
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 表单
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      // 总数据
+      total: 0,
+      // 列表
+      tableData: []
+    }
+  },
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    // 列表
+    getList() {
+      this.loading = true
+      list(this.form).then(res => {
+        if (res.code === 0) {
+          this.tableData = res.data.records
+          this.total = res.data.total
+          this.loading = false
+        }
+      })
+    },
+
+    // 搜索
+    getSearch() {
+      this.form.pageNum = 1
+      this.getList()
+    },
+
+    // 重置
+    getRefresh() {
+      this.form = {
+        pageNum: 1,
+        pageSize: 10
+      }
+      this.getList()
+    },
+
+    // 详情
+    getDetail(row, boolean) {
+      this.$router.push({
+        path: `/operation/activity/detail`,
+        query: {
+          id: row.id,
+          boolean: boolean,
+          activityState: row.activityState
+        }
+      })
+    },
+
+    // 上下架
+    getChange(id, status, title) {
+      change(id, status).then(res => {
+        if (res.code === 0) {
+          this.$message.success(`${title}成功!`)
+          this.getList()
+        }
+      })
+    },
+
+    // 删除
+    getDelete(row) {
+      this.$confirm(`是否删除${row.name}?`, '提示', {
+        type: 'warning'
+      }).then(() => {
+        change(row.id, 2).then(res => {
+          if (res.code === 0) {
+            this.$message.success('删除成功!')
+            this.getList()
+          }
+        })
+      })
+    },
+
+    // 复制链接
+    getCopy() {
+      this.$message.success('复制成功!')
+    },
+
+    // 字典翻译
+    activityFormatter(row) {
+      return this.selectDictLabel(this.currentOptions, row.activityState)
+    },
+
+    statusFormatter(row) {
+      return this.selectDictLabel(this.disabledOptions, row.status)
+    }
+  }
+}
+</script>

+ 112 - 0
src/views/operation/agreement/detail.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class='app-container'>
+    <el-form :model="form" ref="form" :rules="rules" label-width="100px" :disabled="disabled">
+      <el-form-item label="协议名称:" prop="name">
+        <el-input v-model="form.name" placeholder="请输入协议名称" />
+      </el-form-item>
+      <el-form-item label="协议类型:" prop="type">
+        <el-select v-model="form.type" placeholder="请选择协议类型">
+          <el-option v-for="item in typeOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="协议内容:" prop="content">
+        <Editor v-model="form.content" :height="500" :readOnly="disabled" />
+      </el-form-item>
+    </el-form>
+    <div class="form-btn">
+      <el-button @click="cancel">取消</el-button>
+      <el-button v-if="!disabled" type="primary" @click="getSubmit">确定</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import Editor from "@/components/Editor/index";
+import { submit, detail } from '@/api/operation/agreement'
+export default {
+  components: {
+    Editor
+  },
+  data() {
+    return {
+      // 表单
+      form: {},
+      // 协议类型
+      typeOptions: [{
+        value: 0,
+        label: '用户协议'
+      }, {
+        value: 1,
+        label: '隐私协议'
+      }, {
+        value: 2,
+        label: '音乐服务许可协议'
+      }, {
+        value: 3,
+        label: '儿童隐私保护指南'
+      }, {
+        value: 4,
+        label: 'VIP音乐服务协议'
+      }, {
+        value: 5,
+        label: '爱听付费协议'
+      }],
+      // 校验
+      rules: {
+        name: [{
+          required: true, message: '请输入协议名称', trigger: 'blur'
+        }],
+        type: [{
+          required: true, message: '请选择协议类型', trigger: 'change'
+        }],
+        content: [{
+          required: true, message: '请输入协议内容', trigger: 'blur'
+        }]
+      },
+      // 只读
+      disabled: this.$route.query.boolean ? true : false
+    }
+  },
+  mounted() {
+    if (this.$route.query.id) {
+      this.getDetail()
+    }
+  },
+  methods: {
+    // 详情
+    getDetail() {
+      detail(this.$route.query.id).then(res => {
+        if (res.code === 0) {
+          this.form = res.data
+        }
+      })
+    },
+
+    // 取消
+    cancel() {
+      this.$tab.closeOpenPage('/operation/agreement')
+    },
+    // 提交
+    getSubmit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          submit(this.form).then(res => {
+            if (res.code === 0) {
+              this.$message.success('提交成功!')
+              this.cancel()
+            }
+          })
+        } else {
+          return false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-form-item {
+  width: 500px;
+}
+</style>

+ 109 - 0
src/views/operation/agreement/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class='app-container'>
+    <el-button type="primary" icon="el-icon-plus" size="mini" @click="getDetail()" v-hasPermi="['operation:agreement:add']">新增</el-button>
+    <!-- 列表 -->
+    <el-table :data="tableData" v-loading="loading">
+      <el-table-column label="序号" align="center" type="index" />
+      <el-table-column label="协议名称" align="center" prop="name" />
+      <el-table-column label="协议类型" align="center" prop="type" :formatter="typeFormatter" />
+      <el-table-column label="更新时间" align="center" prop="updateTime" />
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" @click="getDetail(scope.row.id, true)">查看</el-button>
+          <el-button type="text" @click="getDetail(scope.row.id)" v-hasPermi="['operation:agreement:edit']">编辑</el-button>
+          <el-button type="delete" @click="getDelete(scope.row)" v-hasPermi="['operation:agreement:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+      @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { list, remove } from '@/api/operation/agreement'
+export default {
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 表单
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      // 列表
+      tableData: [],
+      // 总数据
+      total: 0,
+      // 协议类型
+      typeOptions: [{
+        value: 0,
+        label: '用户协议'
+      }, {
+        value: 1,
+        label: '隐私协议'
+      }, {
+        value: 2,
+        label: '音乐服务许可协议'
+      }, {
+        value: 3,
+        label: '儿童隐私保护指南'
+      }, {
+        value: 4,
+        label: 'VIP音乐服务协议'
+      }, {
+        value: 5,
+        label: '爱听付费协议'
+      }]
+    }
+  },
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    // 列表
+    getList() {
+      this.loading = true
+      list(this.form).then(res => {
+        if (res.code === 0) {
+          this.tableData = res.data.records
+          this.total = res.data.total
+          this.loading = false
+        }
+      })
+    },
+
+    // 详情
+    getDetail(id, boolean) {
+      this.$router.push({
+        path: `/operation/agreement/detail`,
+        query: {
+          id: id,
+          boolean: boolean
+        }
+      })
+    },
+
+    // 删除
+    getDelete(row) {
+      this.$confirm(`是否删除${row.name}?`, '提示', {
+        type: 'warning'
+      }).then(() => {
+        remove(row.id).then(res => {
+          if (res.code === 0) {
+            this.$message.success('删除成功!')
+            this.getList()
+          }
+        })
+      }).catch(() => { })
+    },
+
+    // 字典翻译
+    typeFormatter(row) {
+      return this.selectDictLabel(this.typeOptions, row.type)
+    }
+  }
+}
+</script>

+ 1 - 1
src/views/operation/recommend/detail.vue

@@ -559,7 +559,7 @@ export default {
 
     // 返回
     cancel() {
-      this.$tab.closeOpenPage("/operation/operationRecommend");
+      this.$tab.closeOpenPage("/operation/homePage/operationRecommend");
     },
 
     // 字典翻译

+ 1 - 1
src/views/operation/tag/index.vue

@@ -45,7 +45,7 @@
             新增
           </el-button>
           <el-button type="text" v-if="scope.row.status === 0" @click="getChange(scope.row, 1)"
-            v-hasPermi="['operation:tag:down']">禁用</el-button>
+            v-hasPermi="['operation:tag:down']">下架</el-button>
           <span v-else>
             <el-button type="text" @click="getDialog('编辑', scope.row)" style="margin-left: 10px"
               v-hasPermi="['operation:tag:edit']">

+ 1 - 1
src/views/order/list/index.vue

@@ -121,7 +121,7 @@
         <el-table-column label="操作人" prop="useName" align="center" />
         <el-table-column label="操作" align="center">
           <template slot-scope="scope">
-            <el-button type="text" @click="getDownLoad(scope.row)" :disabled="scope.row.status === 1">下载</el-button>
+            <el-button type="text" @click="getDownLoad(scope.row)">下载</el-button>
           </template>
         </el-table-column>
       </el-table>

+ 7 - 5
src/views/project/list/detail.vue

@@ -11,7 +11,7 @@
       <el-form-item v-if="projectForm.id" label="版本管理:" style="width: 100%;">
         <!-- app列表 -->
         <el-button type="primary" icon="el-icon-plus" @click="getDialog()">新增</el-button>
-        <el-table :data="versionData" v-loading="loading" height="342px">
+        <el-table :data="versionData" v-loading="loading">
           <el-table-column type="index" label="序号" align="center" />
           <el-table-column prop="name" label="应用名称" align="center" show-overflow-tooltip />
           <el-table-column prop="version" label="最新版本" align="center" show-overflow-tooltip />
@@ -41,8 +41,8 @@
             </template>
           </el-table-column>
         </el-table>
-        <pagination v-show="total > 0" :total="total" :page.sync="versionForm.pageNum"
-          :limit.sync="versionForm.pageSize" @pagination="getList" />
+        <pagination v-show="total > 0" :total="total" :page.sync="versionForm.pageNum" :limit.sync="versionForm.pageSize"
+          @pagination="getList" />
       </el-form-item>
     </el-form>
     <div class="form-btn">
@@ -323,8 +323,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.el-form-item {
-  width: 500px;
+.form {
+  .el-form-item {
+    width: 500px;
+  }
 }
 
 ::v-deep .updateContent {

+ 6 - 8
src/views/service/code/index.vue

@@ -3,17 +3,16 @@
     <!-- 搜索 -->
     <el-form inline size="mini">
       <el-form-item label="批次号:">
-        <el-input v-model="form.no" placeholder="请输入批次号" />
+        <el-input v-model="form.no" placeholder="请输入批次号" clearable />
       </el-form-item>
       <el-form-item label="服务时长:">
-        <el-select v-model="form.serviceTime" placeholder="请选择服务时长">
-          <el-option v-for="item in serviceTimeOptions" :key="item.value" :label="item.label"
-            :value="item.value" />
+        <el-select v-model="form.serviceTime" placeholder="请选择服务时长" clearable>
+          <el-option v-for="item in serviceTimeOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </el-form-item>
       <el-form-item label="创建时间:">
         <el-date-picker v-model="date" type="datetimerange" start-placeholder="开始时间" end-placeholder="结束时间"
-          value-format="yyyy-MM-dd HH:mm:ss" clearable />
+          value-format="yyyy-MM-dd HH:mm:ss" />
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" @click="getSearch">搜索</el-button>
@@ -36,7 +35,7 @@
         </template>
       </el-table-column>
     </el-table>
-    <pagination v-show="total>0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
       @pagination="getList" />
     <!-- 弹窗 -->
     <el-dialog :visible.sync="dialogVisible" title="新增" width="500px" :before-close="cancel">
@@ -46,8 +45,7 @@
         </el-form-item>
         <el-form-item label="服务时长:" prop="serviceTime">
           <el-select v-model="dialogForm.serviceTime" placeholder="请选择服务时长">
-            <el-option v-for="item in serviceTimeOptions" :key="item.value" :label="item.label"
-              :value="item.value" />
+            <el-option v-for="item in serviceTimeOptions" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
         <el-form-item>

+ 114 - 0
src/views/service/recovery/index.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class='app-container'>
+    <!-- 搜索 -->
+    <el-form inline size="mini">
+      <el-form-item label="兑换编码:">
+        <el-input v-model="form.code" placeholder="请输入兑换编码" />
+      </el-form-item>
+      <el-form-item label="设备标识:">
+        <el-input v-model="form.deviceMac" placeholder="请输入设备标识" />
+      </el-form-item>
+      <el-form-item label="dsn状态:">
+        <el-select v-model="form.dsnStatus" placeholder="请选择dsn状态" clearable>
+          <el-option v-for="item in statusOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="音乐VIP状态:">
+        <el-select v-model="form.vipStatus" placeholder="请选择音乐VIP状态" clearable>
+          <el-option v-for="item in statusOptions" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="getSearch">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="getRefresh">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 列表 -->
+    <el-table :data="tableData" v-loading="loading">
+      <el-table-column label="创建时间" align="center" prop="createTime"></el-table-column>
+      <el-table-column label="兑换码编码" align="center" prop="code"></el-table-column>
+      <el-table-column label="设备标识" align="center" prop="deviceMac"></el-table-column>
+      <el-table-column label="设备型号" align="center" prop="clientType"></el-table-column>
+      <el-table-column label="用户账号" align="center" prop="userName"></el-table-column>
+      <el-table-column label="用户手机号" align="center" prop="phone"></el-table-column>
+      <el-table-column label="服务时长" align="center" prop="serviceTime" :formatter="timeFormatter"></el-table-column>
+      <el-table-column label="VIP领取时间" align="center" prop="vipReceiveTime"></el-table-column>
+      <el-table-column label="音乐VIP状态" align="center" prop="vipStatus" :formatter="statusFormatter"></el-table-column>
+      <el-table-column label="dsn激活时间" align="center" prop="dsnActiveTime"></el-table-column>
+      <el-table-column label="dsn状态" align="center" prop="dsnStatus" :formatter="statusFormatter"></el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+      @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { list } from '@/api/service/recovery'
+import { serviceTimeMixin } from '@/mixin/index'
+export default {
+  mixins: [serviceTimeMixin],
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 表单
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      // 总数据
+      total: 0,
+      // 列表
+      tableData: [],
+      // dsn vip
+      statusOptions: [{
+        value: 0,
+        label: '回收成功'
+      }, {
+        value: 1,
+        label: '回收失败'
+      }],
+    }
+  },
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    // 列表
+    getList() {
+      this.loading = true
+      list(this.form).then(res => {
+        if (res.code === 0) {
+          this.tableData = res.data.records
+          this.total = res.data.total
+          this.loading = false
+        }
+      })
+    },
+
+    // 搜索
+    getSearch() {
+      this.form.pageNum = 1
+      this.getList()
+    },
+
+    // 重置
+    getRefresh() {
+      this.form = {
+        pageNum: 1,
+        pageSize: 10
+      }
+      this.getList()
+    },
+
+    // 字典翻译
+    statusFormatter(row, column, cellValue) {
+      return this.selectDictLabel(this.statusOptions, cellValue)
+    },
+
+    timeFormatter(row) {
+      return this.selectDictLabel(this.serviceTimeOptions, row.serviceTime)
+    }
+  }
+}
+</script>

+ 7 - 7
src/views/user/list/index.vue

@@ -124,25 +124,25 @@ export default {
         label: '未知'
       }, {
         value: 0,
-        label: '小米应用市场'
+        label: '小米'
       }, {
         value: 1,
-        label: '360应用市场'
+        label: '360'
       }, {
         value: 2,
-        label: '腾讯应用市场'
+        label: '应用宝'
       }, {
         value: 3,
-        label: 'OPPP应用市场'
+        label: 'OPPP'
       }, {
         value: 4,
-        label: 'ViVO应用市场'
+        label: 'ViVO'
       }, {
         value: 5,
-        label: '魅族应用市场'
+        label: '魅族'
       }, {
         value: 6,
-        label: '华为应用市场'
+        label: '华为'
       }, {
         value: 7,
         label: '微信小程序'

+ 152 - 0
src/views/user/store/index.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class='app-container'>
+    <el-button type="primary" icon="el-icon-plus" size="mini" @click="dialogVisible = true" v-hasPermi="['user:store:add']">新增</el-button>
+    <!-- 列表 -->
+    <el-table :data="tableData" v-loading="loading">
+      <el-table-column label="账号ID" align="center" prop="id" />
+      <el-table-column label="账号名称" align="center" prop="name" />
+      <el-table-column label="手机号" align="center" prop="phone" />
+      <el-table-column label="性别" align="center" prop="sex" :formatter="sexFormatter" />
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <el-button type="delete" @click="getDelete(scope.row)" v-hasPermi="['user:store:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" :page.sync="form.pageNum" :limit.sync="form.pageSize"
+      @pagination="getList" />
+    <!-- 弹窗 -->
+    <el-dialog :visible.sync="dialogVisible" title="新增" width="500px" :before-close="cancel">
+      <el-form :model="dialogForm" ref="dialogForm" :rules="rules" label-width="100px">
+        <el-form-item label="账号名称:" prop="name">
+          <el-input v-model="dialogForm.name" placeholder="请输入账号名称" />
+        </el-form-item>
+        <el-form-item label="手机号:" prop="phone">
+          <el-input v-model="dialogForm.phone" placeholder="请输入手机号" />
+        </el-form-item>
+        <el-form-item label="性别:" prop="sex">
+          <el-radio-group v-model="dialogForm.sex">
+            <el-radio v-for="item in sexOptions.slice(0, 2)" :key="item.value" :label="item.value">
+              {{ item.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="getSubmit">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list, submit, remove } from '@/api/user/store'
+import { sexMixin } from '@/mixin/index'
+export default {
+  mixins: [sexMixin],
+  data() {
+    const checkPhone = (rule, value, callback) => {
+      if (!value) {
+        return callback(new Error('请输入手机号'))
+      } else {
+        const reg = /^1[3-9][0-9]\d{8}$/
+        if (reg.test(value)) {
+          callback()
+        } else {
+          return callback(new Error('请输入正确的手机号'))
+        }
+      }
+    }
+    return {
+      // 遮罩层
+      loading: false,
+      // 表单
+      form: {
+        pageNum: 1,
+        pageSize: 10
+      },
+      // 列表
+      tableData: [],
+      // 总数据
+      total: 0,
+      // 弹窗
+      dialogVisible: false,
+      // 弹窗表单
+      dialogForm: {},
+      // 校验
+      rules: {
+        name: [{
+          required: true, message: '请输入账户名称', trigger: 'blur'
+        }],
+        phone: [{
+          required: true, validator: checkPhone, trigger: 'blur'
+        }],
+        sex: [{
+          required: true, message: '请选择性别', trigger: 'change'
+        }]
+      }
+    }
+  },
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    // 列表
+    getList() {
+      this.loading = true
+      list(this.form).then(res => {
+        if (res.code === 0) {
+          this.tableData = res.data.records
+          this.total = res.data.total
+          this.loading = false
+        }
+      })
+    },
+
+    // 取消
+    cancel() {
+      this.$refs.dialogForm.resetFields()
+      this.dialogForm = {}
+      this.dialogVisible = false
+    },
+
+    // 提交
+    getSubmit() {
+      this.$refs.dialogForm.validate((valid) => {
+        if (valid) {
+          submit(this.dialogForm).then(res => {
+            if (res.code === 0) {
+              this.$message.success('提交成功!')
+              this.cancel()
+              this.getList()
+            }
+          })
+        } else {
+          return false
+        }
+      })
+    },
+
+    // 删除
+    getDelete(row) {
+      this.$confirm(`是否删除${row.name}?`, '提示', {
+        type: 'warning'
+      }).then(() => {
+        remove(row.id).then(res => {
+          if (res.code === 0) {
+            this.$message.success('删除成功!')
+            this.getList()
+          }
+        })
+      }).catch(() => { })
+    },
+
+    // 字典翻译
+    sexFormatter(row) {
+      return this.selectDictLabel(this.sexOptions, row.sex)
+    }
+  }
+}
+</script>

+ 1 - 1
vue.config.js

@@ -37,7 +37,7 @@ module.exports = {
       [process.env.VUE_APP_BASE_API]: {
         target: `https://o3tapi.radio1964.com/admin`,
         // target: `http://192.168.0.217:2678/admin`, // 付新保本地
-        // target: `http://192.168.199.74:2027`, // 陈浩
+        // target: `http://192.168.199.208:2027`, // 陈浩
         // target: `http://10.0.2.24:2027`,
         changeOrigin: true,
         pathRewrite: {