ソースを参照

Merge branch 'master' of http://git.derkj.com:9095/waibao/NN2025081602

* 'master' of http://git.derkj.com:9095/waibao/NN2025081602:
  no message
wesmiler 6 ヶ月 前
コミット
71cb9fa2a0

+ 292 - 0
addons/admin/src/views/system/institution.vue

@@ -0,0 +1,292 @@
+<template>
+    <div class="ele-body">
+        <el-card shadow="never">
+            <!-- 搜索表单 -->
+            <el-form :model="table.where" label-width="100px" class="ele-form-search"
+                @keyup.enter.native="$refs.table.reload()" @submit.native.prevent>
+                <el-row :gutter="15">
+                    <el-col :md="6" :sm="12">
+                        <el-form-item label="院校名称">
+                            <el-input v-model="table.where.keyword" placeholder="请输入院校名称或代码" clearable />
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
+                        <el-form-item label="链接类型">
+                            <el-select v-model="table.where.link_type" placeholder="请选择链接类型" clearable>
+                                <el-option label="Web链接" :value="1" />
+                                <el-option label="小程序链接" :value="2" />
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
+                        <el-form-item label="状态">
+                            <el-select v-model="table.where.status" placeholder="请选择状态" clearable>
+                                <el-option label="正常" :value="1" />
+                                <el-option label="不显示" :value="2" />
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
+                        <div class="ele-form-actions">
+                            <el-button type="primary" @click="$refs.table.reload()" icon="el-icon-search"
+                                class="ele-btn-icon">查询</el-button>
+                            <el-button @click="resetSearch">重置</el-button>
+                        </div>
+                    </el-col>
+                </el-row>
+            </el-form>
+
+            <!-- 操作按钮 -->
+            <div class="ele-table-tool ele-table-tool-default">
+                <el-button type="primary" v-if="permission.includes(permissionMap['add'])" icon="el-icon-plus"
+                    @click="openForm()">
+                    新增院校
+                </el-button>
+                <el-button type="danger" v-if="permission.includes(permissionMap['delete'])" icon="el-icon-delete"
+                    @click="batchRemove()">
+                    批量删除
+                </el-button>
+            </div>
+
+            <!-- 数据表格 -->
+            <ele-data-table ref="table" :config="table" :choose.sync="choose" height="calc(100vh - 280px)"
+                highlight-current-row>
+                <template slot-scope="{ row }">
+                    <el-table-column type="selection" width="45" align="center" fixed="left" />
+                    <el-table-column type="index" label="编号" width="60" align="center" fixed="left" />
+                    <el-table-column prop="name" label="院校名称" min-width="150" />
+                    <el-table-column prop="code" label="院校代码" width="120" />
+                    <el-table-column prop="thumb" label="封面/Logo" width="100">
+                        <template slot-scope="{ row }">
+                            <el-image v-if="row.thumb" :src="row.thumb" style="width: 50px; height: 50px;"
+                                fit="cover" />
+                            <span v-else>-</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="link_type" label="链接类型" width="100">
+                        <template slot-scope="{ row }">
+                            <el-tag :type="row.link_type === 1 ? 'primary' : 'success'">
+                                {{ row.link_type === 1 ? 'Web链接' : '小程序链接' }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="link_url" label="详情地址" min-width="200" show-overflow-tooltip>
+                        <template slot-scope="{ row }">
+                            <el-link v-if="row.link_url" :href="row.link_url" target="_blank" type="primary">
+                                {{ row.link_url }}
+                            </el-link>
+                            <span v-else>-</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="description" label="介绍" min-width="150" show-overflow-tooltip />
+                    <el-table-column prop="sort" label="排序" width="80" align="center">
+                        <template slot-scope="{ row }">
+                            <el-input-number v-model="row.sort" :min="0" size="mini" @change="updateSort(row)" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="状态" width="80">
+                        <template slot-scope="{ row }">
+                            <el-switch v-model="row.status" :active-value="1" :inactive-value="2"
+                                @change="editStatus(row)" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="create_time" label="创建时间" width="160" />
+                    <el-table-column label="操作" width="160" fixed="right">
+                        <template slot-scope="{ row }">
+                            <el-button v-if="permission.includes(permissionMap['edit'])" type="text" size="mini"
+                                @click="openForm(row)">编辑</el-button>
+                            <el-button v-if="permission.includes(permissionMap['delete'])" type="text" size="mini"
+                                @click="remove(row)">删除</el-button>
+                        </template>
+                    </el-table-column>
+                </template>
+            </ele-data-table>
+        </el-card>
+
+        <!-- 新增/编辑弹窗 -->
+        <el-dialog :title="isEdit ? '编辑院校' : '新增院校'" :visible.sync="formVisible" width="600px">
+            <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
+                <el-form-item label="院校名称" prop="name">
+                    <el-input v-model="formData.name" placeholder="请输入院校名称" />
+                </el-form-item>
+                <el-form-item label="院校代码" prop="code">
+                    <el-input v-model="formData.code" placeholder="请输入院校代码" />
+                </el-form-item>
+                <el-form-item label="封面/Logo">
+                    <uploadImage :limit="1" v-model="formData.thumb"></uploadImage>
+                </el-form-item>
+                <el-form-item label="链接类型" prop="link_type">
+                    <el-select v-model="formData.link_type" placeholder="请选择链接类型">
+                        <el-option label="Web链接" :value="1" />
+                        <el-option label="小程序链接" :value="2" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="详情地址" prop="link_url">
+                    <el-input v-model="formData.link_url" placeholder="请输入详情地址" />
+                </el-form-item>
+                <el-form-item label="介绍">
+                    <el-input v-model="formData.description" type="textarea" placeholder="请输入院校介绍" rows="3" />
+                </el-form-item>
+                <el-form-item label="排序">
+                    <el-input-number v-model="formData.sort" :min="0" />
+                </el-form-item>
+                <el-form-item label="状态">
+                    <el-switch v-model="formData.status" :active-value="1" :inactive-value="2" />
+                </el-form-item>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button @click="formVisible = false">取消</el-button>
+                <el-button type="primary" @click="saveForm">保存</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import uploadImage from '@/components/uploadImage.vue'
+
+export default {
+    name: "InstitutionPage",
+    components: {
+        uploadImage
+    },
+    data() {
+        return {
+            table: {
+                url: "/institutions/index",
+                where: { keyword: "", link_type: null, status: null },
+            },
+            choose: [],
+            formData: {
+                id: null,
+                name: "",
+                code: "",
+                thumb: "",
+                link_type: 1,
+                link_url: "",
+                description: "",
+                sort: 0,
+                status: 1
+            },
+            isEdit: false,
+            formVisible: false,
+            formRules: {
+                name: [{ required: true, message: "请输入院校名称", trigger: "blur" }],
+                code: [{ required: true, message: "请输入院校代码", trigger: "blur" }],
+                link_type: [{ required: true, message: "请选择链接类型", trigger: "change" }],
+                link_url: [{ required: true, message: "请输入详情地址", trigger: "blur" }],
+            },
+            permissionMap: {
+                delete: "sys:institution:delete",
+                edit: "sys:institution:edit",
+                add: "sys:institution:add",
+                index: "sys:institution:index",
+                status: "sys:institution:status",
+                sort: "sys:institution:sort",
+                options: "sys:institution:options",
+            }
+        };
+    },
+    computed: { ...mapGetters(["permission"]) },
+    methods: {
+        resetSearch() {
+            this.table.where = { keyword: "", link_type: null, status: null };
+            this.$refs.table.reload();
+        },
+        openForm(row) {
+            if (row) {
+                this.isEdit = true;
+                this.formData = {
+                    id: row.id,
+                    name: row.name,
+                    code: row.code,
+                    thumb: row.thumb || "",
+                    link_type: row.link_type,
+                    link_url: row.link_url,
+                    description: row.description || "",
+                    sort: row.sort || 0,
+                    status: row.status
+                };
+            } else {
+                this.isEdit = false;
+                this.formData = {
+                    id: null,
+                    name: "",
+                    code: "",
+                    thumb: "",
+                    link_type: 1,
+                    link_url: "",
+                    description: "",
+                    sort: 0,
+                    status: 1
+                };
+            }
+            this.formVisible = true;
+        },
+        async saveForm() {
+            this.$refs.formRef.validate(async (valid) => {
+                if (!valid) return;
+                try {
+                    const res = await this.$http.post("/institutions/edit", this.formData);
+                    if (res.data.code === 0) {
+                        this.$message.success("保存成功");
+                        this.formVisible = false;
+                        this.$refs.table.reload();
+                    } else {
+                        this.$message.error(res.data.msg);
+                    }
+                } catch (e) {
+                    this.$message.error("保存失败:" + e.message);
+                }
+            });
+        },
+        async remove(row) {
+            const ids = row ? [row.id] : this.choose.map((d) => d.id);
+            if (!ids.length) return this.$message.warning("请选择数据");
+            this.$confirm("确定要删除选中院校吗?", "提示", { type: "warning" }).then(async () => {
+                try {
+                    const res = await this.$http.post("/institutions/delete", { id: ids });
+                    if (res.data.code === 0) {
+                        this.$message.success("删除成功");
+                        this.$refs.table.reload();
+                    } else {
+                        this.$message.error(res.data.msg);
+                    }
+                } catch (e) {
+                    this.$message.error("删除失败:" + e.message);
+                }
+            });
+        },
+        batchRemove() {
+            this.remove();
+        },
+        async editStatus(row) {
+            try {
+                const res = await this.$http.post("/institutions/status", { id: row.id, status: row.status });
+                if (res.data.code !== 0) {
+                    this.$message.error(res.data.msg);
+                    // 恢复原状态
+                    row.status = row.status === 1 ? 2 : 1;
+                }
+            } catch (e) {
+                this.$message.error("状态更新失败:" + e.message);
+                // 恢复原状态
+                row.status = row.status === 1 ? 2 : 1;
+            }
+        },
+        async updateSort(row) {
+            try {
+                const res = await this.$http.post("/institutions/sort", { id: row.id, sort: row.sort });
+                if (res.data.code !== 0) {
+                    this.$message.error(res.data.msg);
+                }
+            } catch (e) {
+                this.$message.error("排序更新失败:" + e.message);
+            }
+        }
+    },
+};
+</script>
+
+<style scoped></style>

+ 4 - 1
addons/admin/src/views/video/component/CoursesForm.vue

@@ -23,7 +23,10 @@
 
                 <!-- 费用 -->
                 <el-form-item label="费用" prop="fee">
-                    <el-input-number v-model="formData.fee" :min="0" />
+                    <el-radio-group v-model="formData.fee">
+                        <el-radio :label="0">免费</el-radio>
+                        <el-radio :label="1">收费</el-radio>
+                    </el-radio-group>
                 </el-form-item>
 
                 <!-- 封面图 -->

+ 18 - 3
addons/admin/src/views/video/courses.vue

@@ -25,6 +25,15 @@
                         </el-form-item>
                     </el-col>
                     <el-col :md="6" :sm="12">
+                        <el-form-item label="费用类型:">
+                            <el-select v-model="tableCourses.where.fee" placeholder="请选择费用类型" clearable
+                                class="ele-fluid">
+                                <el-option label="免费" :value="0" />
+                                <el-option label="收费" :value="1" />
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="6" :sm="12">
                         <div class="ele-form-actions">
                             <el-button type="primary" @click="$refs.tableCourses.reload()" icon="el-icon-search"
                                 class="ele-btn-icon">查询</el-button>
@@ -53,7 +62,13 @@
                             <span>{{ row.video_name }}</span> <!-- 显示视频课名称 -->
                         </template>
                     </el-tableCourses-column>
-                    <el-tableCourses-column prop="fee" label="费用" width="100" />
+                    <el-tableCourses-column label="费用" width="100">
+                        <template slot-scope="{ row }">
+                            <el-tag :type="row.fee === 0 ? 'success' : 'warning'">
+                                {{ row.fee === 0 ? '免费' : '收费' }}
+                            </el-tag>
+                        </template>
+                    </el-tableCourses-column>
                     <el-tableCourses-column prop="poster" label="封面" width="120">
                         <template slot-scope="{ row }">
                             <el-image :src="row.poster" style="width:60px;height:40px;" fit="cover" />
@@ -99,7 +114,7 @@ export default {
         return {
             tableCourses: {
                 url: "/videosCourses/index",  // 更新后的接口路径
-                where: { video_id: null, video_name: null, status: null, keyword: null }
+                where: { video_id: null, video_name: null, status: null, fee: null, keyword: null }
             },
             chooseCourses: [],
             formDialogVisibleCourses: false,
@@ -116,7 +131,7 @@ export default {
             this.formDialogVisibleCourses = true;
         },
         resetSearchCourses() {
-            this.tableCourses.where = {};
+            this.tableCourses.where = { video_id: null, video_name: null, status: null, fee: null, keyword: null };
             this.$refs.tableCourses.reload();
         },
         editStatusCourses(row) {

+ 7 - 3
addons/admin/src/views/vip/vip.vue

@@ -62,6 +62,7 @@
                         </template>
                     </el-table-column>
                     <el-table-column prop="price" label="VIP价格" width="120" />
+                    <el-table-column prop="original_price" label="原价" width="120" />
                     <el-table-column prop="day" label="VIP时长/天" width="120" />
                     <el-table-column label="状态" width="80">
                         <template slot-scope="{ row }">
@@ -91,6 +92,9 @@
                 <el-form-item label="VIP价格" prop="price">
                     <el-input-number v-model="formData.price" :min="0" />
                 </el-form-item>
+                <el-form-item label="原价" prop="original_price">
+                    <el-input-number v-model="formData.original_price" :min="0" />
+                </el-form-item>
                 <el-form-item label="备注" prop="remark">
                     <el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
                 </el-form-item>
@@ -151,7 +155,7 @@ export default {
                 where: { keyword: "", type: null, status: null },
             },
             choose: [],
-            formData: { id: null, name: "", type: 1, price: 0, day: 0, status: 1 },
+            formData: { id: null, name: "", type: 1, price: 0, original_price: 0, day: 0, status: 1 },
             isEdit: false,
             formVisible: false,
             formRules: {
@@ -187,10 +191,10 @@ export default {
         openForm(row) {
             if (row) {
                 this.isEdit = true;
-                this.formData = { id: row.id, name: row.name, price: row.price, remark: row.remark || "" };
+                this.formData = { id: row.id, name: row.name, price: row.price, original_price: row.original_price || 0, remark: row.remark || "" };
             } else {
                 this.isEdit = false;
-                this.formData = { id: null, name: "", price: 0, remark: "" };
+                this.formData = { id: null, name: "", price: 0, original_price: 0, remark: "" };
             }
             this.formVisible = true;
         },

+ 10 - 11
app/Models/VideoCoursesModel.php

@@ -24,17 +24,17 @@ class VideoCoursesModel extends BaseModel
 
     public function getPosterAttribute($value)
     {
-        return $value? get_image_url($value) : '';
+        return $value ? get_image_url($value) : '';
     }
 
     public function setPosterAttribute($value)
     {
-        return $value? get_image_path($value) : '';
+        return $value ? get_image_path($value) : '';
     }
 
     public function getFeeAttribute($value)
     {
-        return $value? floatval($value) : 0.00;
+        return $value ? floatval($value) : 0.00;
     }
 
     /**
@@ -45,8 +45,8 @@ class VideoCoursesModel extends BaseModel
     {
         return $this->hasOne(VideoModel::class, 'id', 'video_id')
             ->with(['category'])
-            ->where(['status'=>1,'mark'=>1])
-            ->select(['id', 'video_name', 'category_id','type','poster','description', 'status']);
+            ->where(['status' => 1, 'mark' => 1])
+            ->select(['id', 'video_name', 'category_id', 'type', 'poster', 'description', 'status']);
     }
 
     /**
@@ -56,9 +56,9 @@ class VideoCoursesModel extends BaseModel
     public function vip()
     {
         return $this->hasOne(VideoOrderModel::class, 'goods_id', 'id')
-            ->where('expired_at','>', date('Y-m-d H:i:s'))
-            ->where(['status'=>2,'mark'=>1])
-            ->select(['id', 'order_no', 'goods_id','total','expired_at', 'status']);
+            ->where('expired_at', '>', date('Y-m-d H:i:s'))
+            ->where(['status' => 2, 'mark' => 1])
+            ->select(['id', 'order_no', 'goods_id', 'total', 'expired_at', 'status']);
     }
 
     /**
@@ -68,7 +68,7 @@ class VideoCoursesModel extends BaseModel
     public function courses()
     {
         return $this->hasMany(VideoCoursesModel::class, 'video_id', 'video_id')
-            ->where(['status'=>1,'mark'=>1]);
+            ->where(['status' => 1, 'mark' => 1]);
     }
 
     /**
@@ -78,7 +78,6 @@ class VideoCoursesModel extends BaseModel
     public function learns()
     {
         return $this->hasMany(VideoLearnLogModel::class, 'video_id', 'video_id')
-            ->where(['status'=>1,'mark'=>1]);
+            ->where(['status' => 1, 'mark' => 1]);
     }
-
 }

+ 87 - 0
app/Models/VideoCoursesModel_BACKUP_1590.php

@@ -0,0 +1,87 @@
+<?php
+// +----------------------------------------------------------------------
+// | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
+// +----------------------------------------------------------------------
+// | 版权所有 2017~2021 LARAVEL研发中心
+// +----------------------------------------------------------------------
+// | 官方网站: http://www.laravel.cn
+// +----------------------------------------------------------------------
+// | Author: laravel开发员 <laravel.qq.com>
+// +----------------------------------------------------------------------
+
+namespace App\Models;
+
+/**
+ * -模型
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Models
+ */
+class VideoCoursesModel extends BaseModel
+{
+    // 设置数据表
+    protected $table = 'videos_courses';
+<<<<<<< HEAD
+=======
+
+    public function getPosterAttribute($value)
+    {
+        return $value? get_image_url($value) : '';
+    }
+
+    public function setPosterAttribute($value)
+    {
+        return $value? get_image_path($value) : '';
+    }
+
+    public function getFeeAttribute($value)
+    {
+        return $value? floatval($value) : 0.00;
+    }
+
+    /**
+     * 课程集
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function collection()
+    {
+        return $this->hasOne(VideoModel::class, 'id', 'video_id')
+            ->with(['category'])
+            ->where(['status'=>1,'mark'=>1])
+            ->select(['id', 'video_name', 'category_id','type','poster','description', 'status']);
+    }
+
+    /**
+     * 是否有效购买单集VIP
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function vip()
+    {
+        return $this->hasOne(VideoOrderModel::class, 'goods_id', 'id')
+            ->where('expired_at','>', date('Y-m-d H:i:s'))
+            ->where(['status'=>2,'mark'=>1])
+            ->select(['id', 'order_no', 'goods_id','total','expired_at', 'status']);
+    }
+
+    /**
+     * 所有课程
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function courses()
+    {
+        return $this->hasMany(VideoCoursesModel::class, 'video_id', 'video_id')
+            ->where(['status'=>1,'mark'=>1]);
+    }
+
+    /**
+     * 学习记录
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function learns()
+    {
+        return $this->hasMany(VideoLearnLogModel::class, 'video_id', 'video_id')
+            ->where(['status'=>1,'mark'=>1]);
+    }
+
+>>>>>>> d64ac277db90f4649e277b9e40eb3d141ae8c846
+}

+ 25 - 0
app/Models/VideoCoursesModel_BASE_1590.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
+// +----------------------------------------------------------------------
+// | 版权所有 2017~2021 LARAVEL研发中心
+// +----------------------------------------------------------------------
+// | 官方网站: http://www.laravel.cn
+// +----------------------------------------------------------------------
+// | Author: laravel开发员 <laravel.qq.com>
+// +----------------------------------------------------------------------
+
+namespace App\Models;
+
+/**
+ * -模型
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Models
+ */
+class VideoCoursesModel extends BaseModel
+{
+    // 设置数据表
+    protected $table = 'videos_courses';
+
+}

+ 24 - 0
app/Models/VideoCoursesModel_LOCAL_1590.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
+// +----------------------------------------------------------------------
+// | 版权所有 2017~2021 LARAVEL研发中心
+// +----------------------------------------------------------------------
+// | 官方网站: http://www.laravel.cn
+// +----------------------------------------------------------------------
+// | Author: laravel开发员 <laravel.qq.com>
+// +----------------------------------------------------------------------
+
+namespace App\Models;
+
+/**
+ * -模型
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Models
+ */
+class VideoCoursesModel extends BaseModel
+{
+    // 设置数据表
+    protected $table = 'videos_courses';
+}

+ 84 - 0
app/Models/VideoCoursesModel_REMOTE_1590.php

@@ -0,0 +1,84 @@
+<?php
+// +----------------------------------------------------------------------
+// | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
+// +----------------------------------------------------------------------
+// | 版权所有 2017~2021 LARAVEL研发中心
+// +----------------------------------------------------------------------
+// | 官方网站: http://www.laravel.cn
+// +----------------------------------------------------------------------
+// | Author: laravel开发员 <laravel.qq.com>
+// +----------------------------------------------------------------------
+
+namespace App\Models;
+
+/**
+ * -模型
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Models
+ */
+class VideoCoursesModel extends BaseModel
+{
+    // 设置数据表
+    protected $table = 'videos_courses';
+
+    public function getPosterAttribute($value)
+    {
+        return $value? get_image_url($value) : '';
+    }
+
+    public function setPosterAttribute($value)
+    {
+        return $value? get_image_path($value) : '';
+    }
+
+    public function getFeeAttribute($value)
+    {
+        return $value? floatval($value) : 0.00;
+    }
+
+    /**
+     * 课程集
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function collection()
+    {
+        return $this->hasOne(VideoModel::class, 'id', 'video_id')
+            ->with(['category'])
+            ->where(['status'=>1,'mark'=>1])
+            ->select(['id', 'video_name', 'category_id','type','poster','description', 'status']);
+    }
+
+    /**
+     * 是否有效购买单集VIP
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function vip()
+    {
+        return $this->hasOne(VideoOrderModel::class, 'goods_id', 'id')
+            ->where('expired_at','>', date('Y-m-d H:i:s'))
+            ->where(['status'=>2,'mark'=>1])
+            ->select(['id', 'order_no', 'goods_id','total','expired_at', 'status']);
+    }
+
+    /**
+     * 所有课程
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function courses()
+    {
+        return $this->hasMany(VideoCoursesModel::class, 'video_id', 'video_id')
+            ->where(['status'=>1,'mark'=>1]);
+    }
+
+    /**
+     * 学习记录
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function learns()
+    {
+        return $this->hasMany(VideoLearnLogModel::class, 'video_id', 'video_id')
+            ->where(['status'=>1,'mark'=>1]);
+    }
+
+}

+ 191 - 0
app/Services/Api/MessageService.php

@@ -0,0 +1,191 @@
+<?php
+// +----------------------------------------------------------------------
+// | LARAVEL8.0 框架 [ LARAVEL ][ RXThinkCMF ]
+// +----------------------------------------------------------------------
+// | 版权所有 2017~2021 LARAVEL研发中心
+// +----------------------------------------------------------------------
+// | 官方网站: http://www.laravel.cn
+// +----------------------------------------------------------------------
+// | Author: laravel开发员 <laravel.qq.com>
+// +----------------------------------------------------------------------
+
+namespace App\Services\Api;
+
+use App\Models\MessageModel;
+use App\Services\BaseService;
+use App\Services\RedisService;
+
+/**
+ * 消息服务-服务类
+ * @author laravel开发员
+ * @since 2020/11/11
+ * @package App\Services\Api
+ */
+class MessageService extends BaseService
+{
+    // 静态对象
+    protected static $instance = null;
+
+    /**
+     * 构造函数
+     * @author laravel开发员
+     * @since 2020/11/11
+     * NoticeService constructor.
+     */
+    public function __construct()
+    {
+        $this->model = new MessageModel();
+    }
+
+    /**
+     * 静态入口
+     */
+    public static function make()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+        return self::$instance;
+    }
+
+    /**
+     * @param $params
+     * @param int $pageSize
+     * @return array
+     */
+    public function getDataList($params, $pageSize = 20)
+    {
+        $query = $this->getQuery($params);
+        $list = $query->select(['a.*'])
+            ->orderBy('a.create_time','desc')
+            ->paginate($pageSize > 0 ? $pageSize : 9999999);
+        $list = $list? $list->toArray() :[];
+        if($list){
+            foreach($list['data'] as &$item){
+                $item['create_time'] = $item['create_time']? datetime($item['create_time'],'Y-m-d H.i.s') : '';
+                $item['time_text'] = $item['create_time']? dateFormat($item['create_time']) : '';
+                $msgType = $item['msg_type']? $item['msg_type'] : 1;
+                if($msgType == 1 || $msgType == 3){
+                    $item['content'] = format_message($item['content']);
+                }else if($msgType==2){
+                    $item['content'] = $item['content']? get_image_url($item['content']) : '';
+                }else if($msgType == 4){
+                    $item['content'] = $item['content']? json_decode($item['content'], true) : [];
+                }
+
+                if(isset($item['from_user']) && $item['from_user']){
+                    $item['from_user_name'] = $item['from_user']['nickname']? $item['from_user']['nickname'] : '用户'.$item['from_uid'];
+                    $item['from_user_avatar'] = $item['from_user']['avatar']? get_image_url($item['from_user']['avatar']) : get_image_url('/images/member/logo.png');
+                }else if($item['from_uid'] <=1){
+                    $item['from_user_name'] = '客服';
+                    $item['from_user_avatar'] = get_image_url('/images/member/custom.png');
+                }
+
+                if(isset($item['to_user']) && $item['to_user']){
+                    $item['to_user_name'] = $item['to_user']['nickname']? $item['to_user']['nickname'] : '用户'.$item['to_uid'];
+                    $item['to_user_avatar'] = $item['to_user']['avatar']? get_image_url($item['to_user']['avatar']) : get_image_url('/images/member/logo.png');
+                }else if($item['to_uid'] <=1){
+                    $item['to_user_name'] = '客服';
+                    $item['to_user_avatar'] = get_image_url('/images/member/custom.png');
+                }
+            }
+
+            if(count($list['data'])) {
+                $this->model->where(['chat_key' => $list['data'][0]['chat_key'], 'is_read' => 2, 'mark' => 1])->where('id', '<=', $list['data'][0]['id'])->update(['is_read' => 1, 'update_time' => time()]);
+                RedisService::keyDel("caches:messages:unread_count*");
+            }
+        }
+
+        return [
+            'pageSize'=> $pageSize,
+            'total'=>isset($list['total'])? $list['total'] : 0,
+            'list'=> isset($list['data'])? array_reverse($list['data']) : []
+        ];
+    }
+
+    /**
+     * 查询
+     * @param $params
+     * @return mixed
+     */
+    public function getQuery($params)
+    {
+        $where = ['a.mark' => 1];
+        $status = isset($params['status'])? $params['status'] : 0;
+        $type = isset($params['type'])? $params['type'] : 0;
+        if($status>0){
+            $where['a.status'] = $status;
+        }
+        if($type>0){
+            $where['a.type'] = $type;
+        }
+
+        return $this->model->with(['fromUser','toUser'])->from('message as a')
+            ->where($where)
+            ->where(function ($query) use($params){
+                $keyword = isset($params['keyword'])? $params['keyword'] : '';
+                if($keyword){
+                    $query->where('a.title','like',"%{$keyword}%");
+                }
+
+
+                $fromUserId= isset($params['from_uid'])? intval($params['from_uid']) : 0;
+                if($fromUserId>0){
+                    $query->where('a.from_uid', $fromUserId);
+                }
+
+                $toUserId= isset($params['to_uid'])? intval($params['to_uid']) : 0;
+                if($fromUserId>0){
+                    $query->where('a.to_uid', $toUserId);
+                }
+
+                $isRead= isset($params['is_read'])? intval($params['is_read']) : 0;
+                if($isRead>0){
+                    $query->where('a.is_read', $isRead);
+                }
+
+            }) ->where(function ($query) use($params){
+                $userId = isset($params['user_id'])? intval($params['user_id']) : 0;
+                if($userId>0){
+                    $query->where('a.from_uid',$userId)->orWhere('a.to_uid', $userId);
+                }
+            });
+    }
+
+    /**
+     * 消息推送处理
+     * @param $userId 用户
+     * @param $msgData 消息数据
+     * @return bool
+     */
+    public function pushMessage($userId, $msgData)
+    {
+        return true;
+    }
+
+    /**
+     * 未读通知消息
+     * @param $type
+     * @return array|mixed
+     */
+    public function getUnreadCount($userId)
+    {
+        $cacheKey = "caches:messages:unread_count_{$userId}";
+        $count = RedisService::get($cacheKey);
+        if($count){
+            return $count;
+        }
+
+        $count = $this->model->where(function($query) use($userId){
+            if($userId){
+                $query->where('to_uid',$userId);
+            }
+        })->where(['is_read'=>2,'type'=>1,'status'=>1,'mark'=>1])
+        ->count('id');
+
+        if($count){
+            RedisService::set($cacheKey, $count, rand(300,600));
+        }
+        return $count;
+    }
+}

+ 7 - 0
app/Services/Common/VideosCoursesService.php

@@ -1,4 +1,5 @@
 <?php
+
 namespace App\Services\Common;
 
 use App\Models\VideoCoursesModel;
@@ -53,6 +54,12 @@ class VideosCoursesService extends BaseService
             $where['a.status'] = $status;
         }
 
+        // Fee filtering
+        $fee = isset($params['fee']) ? (int) $params['fee'] : -1;
+        if ($fee >= 0) {
+            $where['a.fee'] = $fee;
+        }
+
         // Query with JOIN for video details, using course name to filter videos
         $query = $this->model->from('videos_courses as a')
             ->leftJoin('videos as v', 'a.video_id', '=', 'v.id')  // Join with lev_videos table

+ 2 - 15
app/Services/Common/VipService.php

@@ -1,4 +1,5 @@
 <?php
+
 namespace App\Services\Common;
 
 use App\Models\VipModel;
@@ -144,6 +145,7 @@ class VipService extends BaseService
             'name' => $data['name'] ?? '',
             'type' => $data['type'] ?? 1,
             'price' => $data['price'] ?? 0,
+            'original_price' => $data['original_price'] ?? 0,
             'day' => $data['day'] ?? 0,
             'status' => $data['status'] ?? 1,
             'remark' => $data['remark'] ?? '',
@@ -153,18 +155,6 @@ class VipService extends BaseService
     }
 
     /**
-     * 删除VIP套餐
-     * @return array
-     */
-    public function delete()
-    {
-        // 删除VIP套餐(假设7天前的套餐已经不再有效)
-        $this->model->where('mark', 0)->where('update_time', '<=', time() - 7 * 86400)->delete();
-        return parent::delete();
-    }
-
-
-    /**
      * 用户选项
      * @return array
      */
@@ -187,7 +177,6 @@ class VipService extends BaseService
             ->get();
 
         return $datas ? $datas->toArray() : [];
-
     }
 
     // 批量设置VIP接口
@@ -238,7 +227,6 @@ class VipService extends BaseService
             }
 
             return ['code' => 0, 'msg' => '批量设置VIP成功'];
-
         } catch (\Exception $e) {
             return ['code' => 1, 'msg' => '批量设置VIP失败:' . $e->getMessage()];
         }
@@ -255,5 +243,4 @@ class VipService extends BaseService
         $this->model->where('mark', 0)->where('update_time', '<=', time() - 7 * 86400)->delete();
         return parent::delete();
     }
-
 }

BIN
public/app/jdapp_v1.2.30.apk


ファイルの差分が大きいため隠しています
+ 5372 - 646
sql/nn2025081602.sql


BIN
vendor.zip