lvhao 1 dzień temu
rodzic
commit
3c17f7ed95

+ 26 - 0
core/CoreApp/controllers/Aliyuntp.php

@@ -0,0 +1,26 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+class Aliyuntp  extends Start_Controller {
+
+    public function __construct() {
+        parent::__construct();
+        $this->load->library('session');
+        $this->load->_model("Model_aliyunoss","aliyunoss");
+    }
+
+    //定义方法的调用规则 获取URI第二段值
+    public function _remap($arg,$arg_array)
+    {
+        if($arg == 'get_oss_sign')//添加
+        {
+             $this->_get_oss_sign();
+        }else{
+            defined('BASEPATH') OR exit('No direct script access allowed');
+        }
+    }
+    private function _get_oss_sign(){
+        $res = $this->aliyunoss->getOssSign();
+
+        echo json_encode($res,JSON_UNESCAPED_UNICODE);
+    }
+}

+ 166 - 0
core/CoreApp/controllers/Goodimglibrary.php

@@ -0,0 +1,166 @@
+<?php defined('BASEPATH') OR exit('No direct script access allowed');
+class Goodimglibrary extends Start_Controller {
+	public function __construct(){
+		parent::__construct();
+		$this->load->library('session');
+        $this->load->_model('Model_goodimgs','goodimgs');
+    }
+	private $show_url = "http://lyerposs.wepolicy.cn";	
+    public function _remap($arg,$arg_array)
+    {
+		if($arg=="detail"){
+			//$this->_detial($arg_array);
+		}elseif($arg=="edit"){
+			$this->_editData($arg_array);
+		}else{
+			$this->_index();
+		}
+	}
+	private function _index(){
+		if($this->input->method() == 'post'){
+            $page = $this->input->post('page',true);
+		    $perpage = $this->input->post('perpage',true);
+			$title = $this->input->post('title',true);
+            $zh= $this->input->post('zh',true);
+            if(empty($page))
+		    {
+                $start = 0;
+		    	$perpage = 1;
+            }
+		    else
+		    {
+                $start = ($page - 1)*$perpage;
+            }
+
+            $this->db->select('g.id, g.features, g.title, g.sku, g.zh, g.jm, gi.id as img_id, gi.source_cont');
+            $this->db->from('crowd_goods as g');
+            $this->db->join('crowd_goodimgs as gi', 'g.id = gi.goods_id', 'left');
+            if($title){
+                $this->db->like('g.title', $title);
+            }
+            if($zh){
+                $this->db->like('g.zh', $zh);
+            }
+            $this->db->order_by('g.id', 'asc');
+
+            
+
+            // 分页 (limit, offset)
+            $this->db->limit($perpage, $start);
+    
+            $query = $this->db->get();
+
+            $list =  $query->result_array();
+            $info_list = [];
+            foreach($list as $k => $v){
+                $info_list[$k]['id'] = $v['id'];
+                $info_list[$k]['sku'] = $v['sku'];
+                $info_list[$k]['title'] = $v['title'];
+                $info_list[$k]['zh'] = $v['zh'];
+                //$info_list[$k]['jm'] = $v['jm'];
+                $info_list[$k]['imgs'] = $this->transimg($v['source_cont']);
+                
+               
+               
+               
+                
+                
+            }
+			$this->db->from('crowd_goods');
+            if($title){
+                $this->db->like('title', $title);
+            }
+            if($zh){
+                $this->db->like('zh', $zh);
+            }
+            $total = $this->db->count_all_results();
+			
+		    $pagenum = ceil($total/$perpage);
+		    $over = $total-($start+$perpage);
+		    $rows = array('total'=>$total,'over'=>$over,'pagenum'=>$pagenum,'rows'=>($info_list));
+		    echo json_encode($rows);exit;
+        }else{
+            $this->_Template('goodimglibrary',$this->data);
+        }
+	}
+
+    private function transimg($source_cont){
+        $str = ' ';
+        if(!empty($source_cont)){
+            $imgs = json_decode($source_cont,true);
+          
+            foreach($imgs as $k => $v){
+                $type = $this->_checkVideo($v);
+                if($type == "video"){
+                    $str.= '<video controls="true" width="100" src="'.$this->show_url.$v.'" alt="video image" style="margin-right:3px;"></video>';
+                }else{
+                    $str .= '<img width="100px" src="'.$this->show_url.$v.'" alt="image" style="margin-right:3px;">';
+                }
+            }
+           
+        }
+       return$str;
+    }
+	private function _editData($arg_array){
+        if($this->input->method() == 'post'){
+            $id = $this->input->post('id',true);
+            $img = $this->input->post('img',true);
+            if(empty($img)){
+                $img = [];
+            }
+            foreach($img as $k => $v){
+                $img[$k] = str_replace('http://lyerposs.wepolicy.cn', '', $v);
+            }
+            $info  =  $this->db->get_where('crowd_goods',array('id'=>$id))->row_array();    
+            $images = $this->goodimgs->find("goods_id = ".$id);
+            if(empty($images)){
+                if($this->goodimgs->insert(array('goods_id'=>$id,'features'=>$info['features'],'source_cont'=>json_encode($img)))){
+                    echo json_encode(array('msg'=>'修改成功','success'=>true));exit;
+                }else{
+                    echo json_encode(array('msg'=>'修改失败,请重试1','success'=>false));exit;
+                }
+
+            }else{
+                if($this->goodimgs->save(array('features'=>$info['features'],'source_cont'=>json_encode($img)),$images['id'])){
+                    echo json_encode(array('msg'=>'修改成功','success'=>true));exit;
+                }else{
+                    echo json_encode(array('msg'=>'修改失败,请重试2','success'=>false));exit;
+                }
+            }
+
+        }
+        if($this->input->method() == 'get'){
+            $id = $arg_array[0];
+            $goods = $this->db->get_where('crowd_goods',array('id'=>$id))->row_array();
+            $this->db->where('goods_id',$id);
+            $images = $this->db->get('crowd_goodimgs')->result_array();
+            if(empty($images)){
+                $goods['images'] = [];
+            }else{
+                $imgs = json_decode($images[0]['source_cont'],true);
+                $final_imgs = [];
+                foreach($imgs as $k => $v){
+                    $final_imgs[$k]['url'] = $this->show_url.$v;
+                    $final_imgs[$k]['type'] = $this->_checkVideo($v);
+                }
+                $goods['images'] = $final_imgs;
+            }
+           
+           
+            $this->data['goods'] = $goods;
+            $this->_Template('goodimglibrary_edit',$this->data);
+            
+        }
+    }
+    //根据后缀判断是否是视频
+    private function _checkVideo($str){
+        $hz_arr = explode(".",$str);
+        $hz = end($hz_arr);
+        if(in_array($hz,['mp4','flv','rmvb','avi','wmv','mov','rm','mkv'])){
+            return "video";
+        }
+        return "img";
+
+    }
+	
+}

+ 76 - 0
core/CoreApp/models/Model_aliyunoss.php

@@ -0,0 +1,76 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 
+class Model_aliyunoss extends Lin_Model 
+{
+	function __construct(){
+        parent::__construct();
+        $this->load->_model(" Model_setting","setting");
+    }
+    private $oss_basic_url = "https://lyerp-product-source.oss-cn-beijing.aliyuncs.com";
+    private $show_basie_url ="http://lyerposs.wepolicy.cn"; 
+    private function get_oss_config(){
+        $oss_aliyun_id_info = $this->setting->find('skey = "oss_aliyun_id"');
+        $oss_aliyun_key_info = $this->setting->find('skey = "oss_aliyun_key"');
+        $accessKeyId =  $oss_aliyun_id_info['svalue'];
+        $accessKeySecret = $oss_aliyun_key_info['svalue'];
+        $bucket = "lyerp-product-source";
+        $endpoint = "oss-cn-beijing.aliyuncs.com";
+        $region = "oss-cn-beijing";
+        $host = $bucket.".".$endpoint;
+
+        return [
+            'accessKeyId' => $accessKeyId,
+            'accessKeySecret' => $accessKeySecret,
+            'bucket' => $bucket,
+            'endpoint' => $endpoint,
+            'region'     => $region,
+            'host' => $host,
+        ];
+
+    }
+
+    public function getOssSign(){
+        $conf = $this->get_oss_config();
+        $accessKeyId = $conf['accessKeyId'];
+        $accessKeySecret = $conf['accessKeySecret'];
+        $region = $conf['region'];
+        $bucket = $conf['bucket'];
+        $endpoint = $conf['endpoint'];
+        $host = $conf['host'];
+
+        // 1. 获取OSS的签名信息
+        $date_folder = date('Ymd');
+        $dir = "uploads/{$date_folder}/";
+
+        //1 .设置上传子目录(按日期分类)
+        $expiration = date('Y-m-d\TH:i:s\Z', time() + 300); // 5分钟后过期
+        $conditions = [
+            ["content-length-range", 0, 1048576000], // 限制文件大小,这里设置为最大1GB
+            ["starts-with", '$key', $dir] // 限制文件上传的目录前缀
+        ];
+        $policy = json_encode(['expiration' => $expiration, 'conditions' => $conditions]);
+        $base64Policy = base64_encode($policy);
+        // 2. 计算签名
+        $signature = base64_encode(hash_hmac('sha1', $base64Policy, $accessKeySecret, true));
+
+        // 3. 返回给前端的数据
+        $response = [
+            'accessKeyId'  => $accessKeyId,
+            'accessKeySecret'=>$accessKeySecret,
+            'bucket'    => $bucket,
+            'region'    => $region,
+            'dir'       => $dir,
+            'policy'    => $base64Policy,
+            'signature' => $signature,
+            'host'      => $host,
+            'expire'    => time() + 3600,
+            'oss_url'=>$this->oss_basic_url,
+            'show_url'=>$this->show_basie_url
+        ];
+        return [
+            'code'=>1,
+            'msg'=>'success',
+            'data'=>$response
+        ];
+    }
+
+}  

+ 11 - 0
core/CoreApp/models/Model_goodimgs.php

@@ -0,0 +1,11 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 
+class Model_goodimgs extends Lin_Model 
+{
+	function __construct(){
+		parent::__construct();
+		$this->load->database();
+		$this->table = 'goodimgs';
+		$this->load_table('goodimgs');
+	}
+	
+}  //end class

+ 11 - 0
core/CoreApp/models/Model_goods.php

@@ -0,0 +1,11 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 
+class Model_goods extends Lin_Model 
+{
+	function __construct(){
+		parent::__construct();
+		$this->load->database();
+		$this->table = 'goods';
+		$this->load_table('goods');
+	}
+	
+}  //end class

+ 37 - 0
template/erp/goodimglibrary.html

@@ -0,0 +1,37 @@
+{Template header}
+<style>
+   
+</style>
+<body>
+<div class="warp">
+<div class="fixed">
+<ul class="search">
+<li>商品名称:<input value="" name="title" type="text" ></li>
+<li>仓库名称:<input value="" name="zh" type="text" ></li>
+<li><span>确 定</span></li>
+</ul>
+<!-- <div class="control">
+
+</div> -->
+<table class="datatitle data" border="0" style="border-collapse:collapse;">
+<tr>
+<td><label onClick="swapCheck()"><input name="checkbox" type="checkbox" class="regular-checkbox"></label></td>
+<td>SKU</td>
+<td>商品名称</td>
+<td>仓库名称</td>
+<!-- <td>料号</td> -->
+<td>图片|视频</td>
+</tr>
+</table>
+</div>
+<table class="datatext data" border="0" style="border-collapse:collapse;">
+</table>
+<div class="bomf"></div>
+</div>
+<script>
+var dataurl = "/goodimglibrary";
+var editurl = "/goodimglibrary/edit/";
+var editdj = 1;
+var editt = " 货品图片 - 编辑";
+</script>
+{Template footer}

+ 368 - 0
template/erp/goodimglibrary_edit.html

@@ -0,0 +1,368 @@
+{Template header}
+
+<style>
+   input[type="file"] {
+        display: none;
+    }
+     /* 图片网格 */
+     .image-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+        gap: 1.2rem;
+        margin-top: 1rem;
+    }
+    .image-card {
+        position: relative;
+        background: #f1f5f9;
+        border-radius: 20px;
+        overflow: hidden;
+        box-shadow: 0 4px 8px rgba(0,0,0,0.05);
+        transition: transform 0.2s, box-shadow 0.2s;
+    }
+    .image-card:hover {
+        transform: translateY(-4px);
+        box-shadow: 0 12px 20px -8px rgba(0,0,0,0.2);
+    }
+    .image-card img {
+        width: 100%;
+        aspect-ratio: 1 / 1;
+        object-fit: cover;
+        display: block;
+        background: #e2e8f0;
+    }
+    .delete-btn {
+        position: absolute;
+        top: 8px;
+        right: 8px;
+        background: rgba(0,0,0,0.65);
+        backdrop-filter: blur(4px);
+        border: none;
+        color: white;
+        width: 28px;
+        height: 28px;
+        border-radius: 40px;
+        font-size: 16px;
+        font-weight: bold;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: 0.2s;
+        font-family: monospace;
+    }
+    .delete-btn:hover {
+        background: #dc2626;
+        transform: scale(1.05);
+    }
+    .upload-status {
+        margin-top: 0.8rem;
+        font-size: 0.85rem;
+        color: #2563eb;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+    }
+    .loading-spinner {
+        width: 16px;
+        height: 16px;
+        border: 2px solid #bfdbfe;
+        border-top-color: #2563eb;
+        border-radius: 50%;
+        animation: spin 0.7s linear infinite;
+        display: inline-block;
+    }
+    @keyframes spin {
+        to { transform: rotate(360deg); }
+    }
+    hr {
+        margin: 1.5rem 0;
+        border: none;
+        border-top: 1px solid #e2e8f0;
+    }
+    .info-note {
+        background: #fef9c3;
+        color: #854d0e;
+        font-size: 0.8rem;
+        padding: 0.6rem 1rem;
+        border-radius: 20px;
+        margin-top: 1rem;
+    }
+    button {
+        background: none;
+        border: none;
+    }
+    .btn-outline {
+        background: white;
+        border: 1px solid #cbd5e1;
+        padding: 0.4rem 1rem;
+        border-radius: 40px;
+        font-size: 0.8rem;
+        cursor: pointer;
+    }
+
+</style>
+<body>
+<div class="warp">
+<div class="title winnone">货品图片 - 编辑</div>
+<ul class="setting">
+
+
+
+<li class="length">
+<em>SKU:</em>
+<b>{$goods['sku']}</b>
+</li>
+<li class="length">
+<em>商品名称:</em>
+<b>{$goods['title']}</b>
+</li>
+<li class="length">
+<em>仓库名称:</em>
+<b>{$goods['zh']}</b>
+</li>
+<li class="length">
+<em>料号:</em>
+<b>{$goods['jm']}</b>
+</li>
+
+<li class="length">
+  <em>上传图片</em>
+  <span>
+    <a href="javascript:void(0);" id = "upload-btn"><span style="background-color: #2ca8a1;padding:4px 10px;color:#fff;border-radius: 3px;font-size: 16px;"> <span>点击添加图片</span></span> </a>
+   
+    <input type="file" id="fileInput" accept="image/jpeg,image/png,image/gif,image/webp,image/avif,image/bmp,image/svg+xml,image/tiff,video/mp4,video/webm,video/ogg,video/quicktime,video/x-msvideo,video/3gpp" multiple>
+  </span>
+  
+  
+<!-- 状态显示区 (上传中) -->
+<div id="statusMsg" class="upload-status" style="min-height: 32px;"></div>
+<!-- 图片列表展示区 -->
+<div id="imageGallery" class="image-grid">
+    {foreach $goods['images'] as $img}
+        {if $img['type'] == 'video'}
+            <div class="image-card">
+                <video controls="true" width="150" src="{$img['url']}" alt="video image"></video>
+                <input type="hidden" name="img[]" value="{$img['url']}" />
+                <button class="delete-btn" aria-label="删除文件">✕</button>
+            </div>
+
+        {else}
+            <div class="image-card">
+                <img src="{$img['url']}" alt="uploaded image">
+                <input type="hidden" name="img[]" value="{$img['url']}" />
+                <button class="delete-btn" aria-label="删除文件">✕</button>
+            </div>
+        {/if}
+    
+    {/foreach}
+</div>
+
+
+
+<div style="clear:both;"></div>
+</ul>
+
+<input type="hidden" name="id" value="{$goods['id']}" />
+<div class="button"><font class="datasave">提 交</font> <font class="fh">关 闭</font></div>
+</div>
+
+
+<script>
+
+var addedit="/goodimglibrary/edit/";
+const  uploadZone = document.getElementById("upload-btn");
+const fileInput = document.getElementById('fileInput');
+const statusDiv = document.getElementById('statusMsg');
+const gallery = document.getElementById('imageGallery');
+
+// 向oss上传文件
+async function uploadSingleFile(file, credentials, onProgress) {
+    return new Promise(async (resolve, reject) => {
+        // 生成最终存储路径:目录 + 时间戳 + 随机数 + 原文件名
+        const timestamp = Date.now();
+        const random = Math.floor(Math.random() * 10000);
+        const safeName = `${timestamp}_${random}_${file.name.replace(/\s/g, '_')}`;
+        const objectKey = credentials.dir + safeName;
+
+        // 初始化 OSS 客户端
+        const client = new OSS({
+            region: credentials.region,
+            accessKeyId: credentials.accessKeyId,
+            accessKeySecret: credentials.accessKeySecret,
+            stsToken: credentials.stsToken,
+            bucket: credentials.bucket,
+            secure: true   // 使用 HTTPS
+        });
+
+        try {
+            // 使用分片上传(适合大文件,小文件也会自动处理)
+            const result = await client.multipartUpload(objectKey, file, {
+                progress: (p, checkpoint) => {
+                    const percent = Math.floor(p * 100);
+                    onProgress(percent);
+                }
+            });
+            // 拼接完整的访问 URL
+            //const fileUrl = `https://${credentials.bucket}.${credentials.region}.aliyuncs.com/${objectKey}`;
+            const fileUrl = credentials.show_url+'/'+`${objectKey}`;
+            resolve(fileUrl);
+        } catch (err) {
+            reject(err);
+        }
+    });
+}
+// 显示短暂状态消息
+function showStatus(text, isLoading = false) {
+    if (!statusDiv) return;
+    if (isLoading) {
+        statusDiv.innerHTML = `<span class="loading-spinner"></span><span>${text}</span>`;
+    } else {
+        statusDiv.innerHTML = `<span>✅ ${text}</span>`;
+        setTimeout(() => {
+            if (statusDiv.innerHTML.includes(text)) {
+                statusDiv.innerHTML = '';
+            }
+        }, 2000);
+    }
+}
+
+// 添加图片卡片到画廊
+function addImageToGallery(imageUrl, fileName = '',fileType='') {
+    const card = document.createElement('div');
+    card.className = 'image-card';
+    let img ;
+    // 存储图片URL以便释放 (对于base64无需revoke,但如果是blob URL需要,这里base64无内存问题)
+    if (fileType.includes('image')) {
+        img = document.createElement('img');
+        img.src = imageUrl;
+        img.alt = fileName || 'uploaded image';
+    } else {
+        img = document.createElement('video');
+        img.src = imageUrl;
+        img.controls = true;
+        img.alt = fileName || 'uploaded video';
+        img.width="150"
+    }
+   
+   
+    
+    const delBtn = document.createElement('button');
+    delBtn.innerHTML = '✕';
+    delBtn.className = 'delete-btn';
+    delBtn.setAttribute('aria-label', '删除图片');
+    
+    // 删除事件
+    delBtn.addEventListener('click', (e) => {
+        e.stopPropagation();
+        card.remove();
+        showStatus('图片已删除', false);
+        // 可选:如果未来使用blob URL, 可在这里 revokeObjectURL(imageUrl)
+    });
+    const input = document.createElement('input');
+    input.type = 'hidden';
+    input.name = 'img[]';
+    input.value = imageUrl;
+    
+    card.appendChild(img);
+    card.appendChild(input);
+    card.appendChild(delBtn);
+    gallery.appendChild(card);
+}
+
+// 1. 从后端获取上传凭证(STS 临时授权参数)
+async function getUploadCredentials() {
+    const response = await fetch('/aliyuntp/get_oss_sign');  // 替换成你的后端接口地址
+    if (!response.ok) throw new Error('获取上传凭证失败');
+    const data = await response.json();
+    // 后端需返回: { region, accessKeyId, accessKeySecret, stsToken, bucket, dir }
+    return data;
+}
+
+
+// 处理文件上传的核心流程
+async function handleFiles(files) {
+    if (!files || files.length === 0) return;
+
+    // 获取后端凭证
+    let res = await getUploadCredentials();
+    let credentials = res.data
+    
+    // 遍历所有选择的文件(支持多选)
+    for (let i = 0; i < files.length; i++) {
+        const file = files[i];
+        // 显示每张图片的上传状态(可以统一显示)
+        showStatus(`正在上传: ${file.name}...`, true);
+        const fileId = `upload_${Date.now()}_${i}`;
+        try {
+            //获取上传凭证 
+            const imgDataUrl = await uploadSingleFile(file,credentials, (percent) => {
+                    updateProgress(fileId, percent);
+                });
+            // 上传成功,添加到页面
+            addImageToGallery(imgDataUrl, file.name,file.type);
+            showStatus(`${file.name} 上传成功!`, false);
+        } catch (err) {
+            console.error(err);
+            showStatus(`${file.name} 失败: ${err.message}`, false);
+            // 错误提示2秒后恢复
+            setTimeout(() => {
+                if (statusDiv.innerText.includes(file.name)) statusDiv.innerHTML = '';
+            }, 2500);
+        }
+    }
+    // 清空 input 的值,允许重复上传同一个文件
+    fileInput.value = '';
+    // 最后清空上传中状态如果没有更多任务
+    setTimeout(() => {
+        if (statusDiv.innerHTML.includes('正在上传') === false && statusDiv.innerHTML !== '') {
+            // 保留最后一条成功提示但2秒后已经清空,不做额外处理
+        }
+    }, 500);
+}
+
+function updateProgress(fileId, percent) {
+    console.log(`{$fileId}上传进度: ${percent}%`);
+    // const fill = document.getElementById(`progress-${fileId}`);
+    // if (fill) fill.style.width = percent + '%';
+}
+// 点击上传区域触发文件选择
+uploadZone.addEventListener('click', (e) => {
+    // 防止点到内部冒泡导致重复触发
+    if (e.target === uploadZone || uploadZone.contains(e.target)) {
+        fileInput.click();
+    }
+});
+
+// 文件选择变化时触发上传
+fileInput.addEventListener('change', (e) => {
+    const files = e.target.files;
+    if (files.length) {
+        handleFiles(files);
+    }
+});
+
+// 阻止拖拽默认事件 (让拖拽体验更好, 可选)
+uploadZone.addEventListener('dragover', (e) => {
+    e.preventDefault();
+    uploadZone.style.borderColor = '#3b82f6';
+    uploadZone.style.background = '#eff6ff';
+});
+uploadZone.addEventListener('dragleave', (e) => {
+    e.preventDefault();
+    uploadZone.style.borderColor = '#cbd5e1';
+    uploadZone.style.background = '#f8fafc';
+});
+uploadZone.addEventListener('drop', (e) => {
+    e.preventDefault();
+    uploadZone.style.borderColor = '#cbd5e1';
+    uploadZone.style.background = '#f8fafc';
+    const files = e.dataTransfer.files;
+    if (files.length) {
+        handleFiles(files);
+    }
+});
+
+</script>
+<script type="text/javascript" src="{$theme}js/aliyun-oss-sdk-6.20.0.min.js"></script>
+<script type="text/javascript" src="{$theme}js/ajaxupload.3.5.js"></script>
+{Template footer}